Compare commits
3 Commits
0b02a65bd4
...
eb6a242257
| Author | SHA1 | Date | |
|---|---|---|---|
| eb6a242257 | |||
| f03c720435 | |||
| 45921bfe17 |
11
src/exercises/type_hints.py
Normal file
11
src/exercises/type_hints.py
Normal file
@ -0,0 +1,11 @@
|
||||
def load_scores(data: list[tuple[str, int | str]]) -> dict[str, int]:
|
||||
result: dict[str, int] = {}
|
||||
|
||||
for name, points in data:
|
||||
result[name] = int(points)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(load_scores([("Housi", 40), ("Theodolf", 45), ("Mehmet", 15)]))
|
||||
35
src/lap_detection/api.py
Normal file
35
src/lap_detection/api.py
Normal file
@ -0,0 +1,35 @@
|
||||
from dataclasses import field, dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class Coordinate:
|
||||
lon: float
|
||||
lat: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class Geometry:
|
||||
coordinates: list[list[float]]
|
||||
type: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class Feature:
|
||||
type: str
|
||||
properties: dict
|
||||
geometry: Geometry
|
||||
|
||||
|
||||
@dataclass
|
||||
class Collection:
|
||||
type: str
|
||||
features: list[Feature]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Lap:
|
||||
start_index: int
|
||||
end_index: int
|
||||
distance_m: float
|
||||
coords: List[Coordinate] = field(default_factory=list)
|
||||
BIN
src/lap_detection/data/usecase1-luetzelsee.png
Normal file
BIN
src/lap_detection/data/usecase1-luetzelsee.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
254
src/lap_detection/data/usecase1.json
Normal file
254
src/lap_detection/data/usecase1.json
Normal file
@ -0,0 +1,254 @@
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {},
|
||||
"geometry": {
|
||||
"coordinates": [
|
||||
[
|
||||
8.765906945161646,
|
||||
47.258748443007846
|
||||
],
|
||||
[
|
||||
8.766561810054753,
|
||||
47.259064494933625
|
||||
],
|
||||
[
|
||||
8.767042044310301,
|
||||
47.25977560486854
|
||||
],
|
||||
[
|
||||
8.770243606009103,
|
||||
47.26097064310798
|
||||
],
|
||||
[
|
||||
8.773154116644207,
|
||||
47.261770612311835
|
||||
],
|
||||
[
|
||||
8.773779876431064,
|
||||
47.262817467331416
|
||||
],
|
||||
[
|
||||
8.774842212812928,
|
||||
47.26337051445671
|
||||
],
|
||||
[
|
||||
8.7770542008966,
|
||||
47.262955729654294
|
||||
],
|
||||
[
|
||||
8.779077005788508,
|
||||
47.261819992730636
|
||||
],
|
||||
[
|
||||
8.77968821302224,
|
||||
47.26189900130527
|
||||
],
|
||||
[
|
||||
8.78080875961777,
|
||||
47.26096570498896
|
||||
],
|
||||
[
|
||||
8.779790080895253,
|
||||
47.26043238542155
|
||||
],
|
||||
[
|
||||
8.778887822597824,
|
||||
47.25847683437709
|
||||
],
|
||||
[
|
||||
8.776704939621482,
|
||||
47.25818053246326
|
||||
],
|
||||
[
|
||||
8.775424314941517,
|
||||
47.25800275051935
|
||||
],
|
||||
[
|
||||
8.771378705157787,
|
||||
47.25681752230369
|
||||
],
|
||||
[
|
||||
8.767187569842747,
|
||||
47.25661998168874
|
||||
],
|
||||
[
|
||||
8.765848734950225,
|
||||
47.258081764790205
|
||||
],
|
||||
[
|
||||
8.76619799622648,
|
||||
47.258891654270315
|
||||
],
|
||||
[
|
||||
8.766983834097772,
|
||||
47.25985955471677
|
||||
],
|
||||
[
|
||||
8.769748819201538,
|
||||
47.260886695021895
|
||||
],
|
||||
[
|
||||
8.771960807285268,
|
||||
47.26149901918447
|
||||
],
|
||||
[
|
||||
8.773328747283983,
|
||||
47.26185455899676
|
||||
],
|
||||
[
|
||||
8.773707113666546,
|
||||
47.262842157058344
|
||||
],
|
||||
[
|
||||
8.774929528133953,
|
||||
47.26341495549261
|
||||
],
|
||||
[
|
||||
8.776530308982785,
|
||||
47.263158184547166
|
||||
],
|
||||
[
|
||||
8.778975137916603,
|
||||
47.261933567519776
|
||||
],
|
||||
[
|
||||
8.779760975789031,
|
||||
47.261933567519776
|
||||
],
|
||||
[
|
||||
8.780954285149079,
|
||||
47.26102496238727
|
||||
],
|
||||
[
|
||||
8.779906501320283,
|
||||
47.26043238542155
|
||||
],
|
||||
[
|
||||
8.778858717491573,
|
||||
47.25843732755084
|
||||
],
|
||||
[
|
||||
8.776035522175249,
|
||||
47.25816077894348
|
||||
],
|
||||
[
|
||||
8.773532483027822,
|
||||
47.257469401105396
|
||||
],
|
||||
[
|
||||
8.771931702178989,
|
||||
47.25699530822746
|
||||
],
|
||||
[
|
||||
8.769341347712782,
|
||||
47.25665948987063
|
||||
],
|
||||
[
|
||||
8.767333095374056,
|
||||
47.25663973578335
|
||||
],
|
||||
[
|
||||
8.766634572821516,
|
||||
47.25742989352784
|
||||
],
|
||||
[
|
||||
8.765848734950225,
|
||||
47.25810151833983
|
||||
],
|
||||
[
|
||||
8.766692783034046,
|
||||
47.259365730169066
|
||||
],
|
||||
[
|
||||
8.768148038352706,
|
||||
47.26015584723416
|
||||
],
|
||||
[
|
||||
8.769865239627705,
|
||||
47.26082743746883
|
||||
],
|
||||
[
|
||||
8.771698861327764,
|
||||
47.261261991316445
|
||||
],
|
||||
[
|
||||
8.772513804305333,
|
||||
47.26157802823809
|
||||
],
|
||||
[
|
||||
8.773445167709042,
|
||||
47.26185455899676
|
||||
],
|
||||
[
|
||||
8.773707113666546,
|
||||
47.26282240527772
|
||||
],
|
||||
[
|
||||
8.77498773834651,
|
||||
47.26335570076975
|
||||
],
|
||||
[
|
||||
8.776792254940261,
|
||||
47.26319768785049
|
||||
],
|
||||
[
|
||||
8.778684086853957,
|
||||
47.261913815399794
|
||||
],
|
||||
[
|
||||
8.77917887366155,
|
||||
47.261913815399794
|
||||
],
|
||||
[
|
||||
8.780081131959008,
|
||||
47.261736045991285
|
||||
],
|
||||
[
|
||||
8.78089607493655,
|
||||
47.26104471483848
|
||||
],
|
||||
[
|
||||
8.779935606426562,
|
||||
47.260471890758794
|
||||
],
|
||||
[
|
||||
8.778829612385294,
|
||||
47.25855584794044
|
||||
],
|
||||
[
|
||||
8.776704939621482,
|
||||
47.25827929995211
|
||||
],
|
||||
[
|
||||
8.773328747283983,
|
||||
47.25744964732027
|
||||
],
|
||||
[
|
||||
8.771320494945257,
|
||||
47.256758260196676
|
||||
],
|
||||
[
|
||||
8.769021191542777,
|
||||
47.25671875208849
|
||||
],
|
||||
[
|
||||
8.767216674949026,
|
||||
47.25667924395049
|
||||
],
|
||||
[
|
||||
8.765906945162811,
|
||||
47.25806201123356
|
||||
],
|
||||
[
|
||||
8.766110680907673,
|
||||
47.25904968004181
|
||||
]
|
||||
],
|
||||
"type": "LineString"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src/lap_detection/data/usecase2-la_stadion.png
Normal file
BIN
src/lap_detection/data/usecase2-la_stadion.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
230
src/lap_detection/data/usecase2.json
Normal file
230
src/lap_detection/data/usecase2.json
Normal file
@ -0,0 +1,230 @@
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {},
|
||||
"geometry": {
|
||||
"coordinates": [
|
||||
[
|
||||
8.680274515612865,
|
||||
47.26389838973671
|
||||
],
|
||||
[
|
||||
8.680175818438386,
|
||||
47.264053132092755
|
||||
],
|
||||
[
|
||||
8.680053297808996,
|
||||
47.264117800406325
|
||||
],
|
||||
[
|
||||
8.679849096758915,
|
||||
47.26413858663324
|
||||
],
|
||||
[
|
||||
8.679621072253923,
|
||||
47.264030036247476
|
||||
],
|
||||
[
|
||||
8.679512165026779,
|
||||
47.263729789341
|
||||
],
|
||||
[
|
||||
8.679399854450054,
|
||||
47.26327941578782
|
||||
],
|
||||
[
|
||||
8.67940325780063,
|
||||
47.26311081342081
|
||||
],
|
||||
[
|
||||
8.679512165026779,
|
||||
47.263018428334334
|
||||
],
|
||||
[
|
||||
8.679712962726285,
|
||||
47.262967616467904
|
||||
],
|
||||
[
|
||||
8.679968214037757,
|
||||
47.2630138090758
|
||||
],
|
||||
[
|
||||
8.680117961474764,
|
||||
47.263189340617544
|
||||
],
|
||||
[
|
||||
8.680165608385664,
|
||||
47.263471113717145
|
||||
],
|
||||
[
|
||||
8.680240482104153,
|
||||
47.263815244403105
|
||||
],
|
||||
[
|
||||
8.680260902209596,
|
||||
47.26396536782704
|
||||
],
|
||||
[
|
||||
8.680169011737263,
|
||||
47.264050822508835
|
||||
],
|
||||
[
|
||||
8.680080524615533,
|
||||
47.26412241956851
|
||||
],
|
||||
[
|
||||
8.679842290057849,
|
||||
47.26414089621335
|
||||
],
|
||||
[
|
||||
8.679631282305564,
|
||||
47.2640462033404
|
||||
],
|
||||
[
|
||||
8.679549601886038,
|
||||
47.26386374586352
|
||||
],
|
||||
[
|
||||
8.679410064501724,
|
||||
47.263344085046214
|
||||
],
|
||||
[
|
||||
8.679386241046757,
|
||||
47.263138528915164
|
||||
],
|
||||
[
|
||||
8.679481534869694,
|
||||
47.26304614387709
|
||||
],
|
||||
[
|
||||
8.679665315814304,
|
||||
47.26296299720494
|
||||
],
|
||||
[
|
||||
8.679937583880672,
|
||||
47.26299764166757
|
||||
],
|
||||
[
|
||||
8.680083927966052,
|
||||
47.263131600042726
|
||||
],
|
||||
[
|
||||
8.680189431841683,
|
||||
47.263551949985214
|
||||
],
|
||||
[
|
||||
8.680271112261238,
|
||||
47.26394458153209
|
||||
],
|
||||
[
|
||||
8.6801962385438,
|
||||
47.2640554416769
|
||||
],
|
||||
[
|
||||
8.679971617388333,
|
||||
47.26413858663324
|
||||
],
|
||||
[
|
||||
8.679801449846963,
|
||||
47.26412934831126
|
||||
],
|
||||
[
|
||||
8.679685735919747,
|
||||
47.26408315667672
|
||||
],
|
||||
[
|
||||
8.679580232044117,
|
||||
47.26398384452665
|
||||
],
|
||||
[
|
||||
8.679471324816973,
|
||||
47.26360276129074
|
||||
],
|
||||
[
|
||||
8.67942708125662,
|
||||
47.263376419646136
|
||||
],
|
||||
[
|
||||
8.679382837695215,
|
||||
47.263170863640255
|
||||
],
|
||||
[
|
||||
8.679539391833316,
|
||||
47.26302304759247
|
||||
],
|
||||
[
|
||||
8.679675525867026,
|
||||
47.26296299720494
|
||||
],
|
||||
[
|
||||
8.679883130267626,
|
||||
47.26299995129776
|
||||
],
|
||||
[
|
||||
8.680032877703553,
|
||||
47.26308078828532
|
||||
],
|
||||
[
|
||||
8.680090734668198,
|
||||
47.26315931552659
|
||||
],
|
||||
[
|
||||
8.68018262514056,
|
||||
47.26351730588564
|
||||
],
|
||||
[
|
||||
8.680240482104153,
|
||||
47.26386605545562
|
||||
],
|
||||
[
|
||||
8.680264305560144,
|
||||
47.26398846370054
|
||||
],
|
||||
[
|
||||
8.680124768175858,
|
||||
47.264078537511125
|
||||
],
|
||||
[
|
||||
8.679944390581795,
|
||||
47.26415013453331
|
||||
],
|
||||
[
|
||||
8.6797299794801,
|
||||
47.26409701417123
|
||||
],
|
||||
[
|
||||
8.679573425341943,
|
||||
47.26397460617764
|
||||
],
|
||||
[
|
||||
8.679508761676175,
|
||||
47.26376212370499
|
||||
],
|
||||
[
|
||||
8.679450904712553,
|
||||
47.263494209806254
|
||||
],
|
||||
[
|
||||
8.679382837695215,
|
||||
47.26316162514942
|
||||
],
|
||||
[
|
||||
8.679508761676175,
|
||||
47.26305076313281
|
||||
],
|
||||
[
|
||||
8.679658509113182,
|
||||
47.26296992609943
|
||||
],
|
||||
[
|
||||
8.679876323565509,
|
||||
47.262972235730814
|
||||
]
|
||||
],
|
||||
"type": "LineString"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
146
src/lap_detection/data/usecase3.json
Normal file
146
src/lap_detection/data/usecase3.json
Normal file
@ -0,0 +1,146 @@
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {},
|
||||
"geometry": {
|
||||
"coordinates": [
|
||||
[
|
||||
9.834547025245826,
|
||||
46.80193545785821
|
||||
],
|
||||
[
|
||||
9.835708992332172,
|
||||
46.802531998995164
|
||||
],
|
||||
[
|
||||
9.83625366440333,
|
||||
46.803004256037525
|
||||
],
|
||||
[
|
||||
9.836653090589152,
|
||||
46.804048177947294
|
||||
],
|
||||
[
|
||||
9.83708882824638,
|
||||
46.805340624627235
|
||||
],
|
||||
[
|
||||
9.8373793200183,
|
||||
46.80653362477142
|
||||
],
|
||||
[
|
||||
9.838105549447391,
|
||||
46.807726598457776
|
||||
],
|
||||
[
|
||||
9.83908595917572,
|
||||
46.80879528158499
|
||||
],
|
||||
[
|
||||
9.840320549204648,
|
||||
46.809689976710644
|
||||
],
|
||||
[
|
||||
9.840828909804458,
|
||||
46.810311284014006
|
||||
],
|
||||
[
|
||||
9.841192024519046,
|
||||
46.81028643185974
|
||||
],
|
||||
[
|
||||
9.842281368662782,
|
||||
46.81051010083516
|
||||
],
|
||||
[
|
||||
9.843407024276331,
|
||||
46.8112059539223
|
||||
],
|
||||
[
|
||||
9.844278499590757,
|
||||
46.81185209515593
|
||||
],
|
||||
[
|
||||
9.84467792577658,
|
||||
46.811379915787256
|
||||
],
|
||||
[
|
||||
9.845585712562325,
|
||||
46.81155387708952
|
||||
],
|
||||
[
|
||||
9.845948827276885,
|
||||
46.81073376888085
|
||||
],
|
||||
[
|
||||
9.845767269920316,
|
||||
46.81016217091576
|
||||
],
|
||||
[
|
||||
9.84522259784913,
|
||||
46.809640271815994
|
||||
],
|
||||
[
|
||||
9.845004729019848,
|
||||
46.80894439847279
|
||||
],
|
||||
[
|
||||
9.844605302833997,
|
||||
46.80894439847279
|
||||
],
|
||||
[
|
||||
9.844060630762868,
|
||||
46.808298222306405
|
||||
],
|
||||
[
|
||||
9.843298089862373,
|
||||
46.80767689174925
|
||||
],
|
||||
[
|
||||
9.843007598090423,
|
||||
46.80755262477646
|
||||
],
|
||||
[
|
||||
9.844205876648118,
|
||||
46.807055554014994
|
||||
],
|
||||
[
|
||||
9.844205876648118,
|
||||
46.806483916960445
|
||||
],
|
||||
[
|
||||
9.84464161430526,
|
||||
46.805440042316604
|
||||
],
|
||||
[
|
||||
9.84308022103312,
|
||||
46.805738294281895
|
||||
],
|
||||
[
|
||||
9.840901532747182,
|
||||
46.805713440014216
|
||||
],
|
||||
[
|
||||
9.839703254190908,
|
||||
46.805564314169374
|
||||
],
|
||||
[
|
||||
9.840865221275834,
|
||||
46.80317824442912
|
||||
],
|
||||
[
|
||||
9.842499237490642,
|
||||
46.80133891010621
|
||||
],
|
||||
[
|
||||
9.844060630762868,
|
||||
46.79915151175959
|
||||
]
|
||||
],
|
||||
"type": "LineString"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
src/lap_detection/data/usecase3_davos_gelaende.png
Normal file
BIN
src/lap_detection/data/usecase3_davos_gelaende.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 265 KiB |
BIN
src/lap_detection/data/usecase4-aarau_stadtkurs.png
Normal file
BIN
src/lap_detection/data/usecase4-aarau_stadtkurs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 456 KiB |
150
src/lap_detection/data/usecase4.json
Normal file
150
src/lap_detection/data/usecase4.json
Normal file
@ -0,0 +1,150 @@
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {},
|
||||
"geometry": {
|
||||
"coordinates": [
|
||||
[
|
||||
8.04235788233035,
|
||||
47.39493290884846
|
||||
],
|
||||
[
|
||||
8.042885285759382,
|
||||
47.394196547811134
|
||||
],
|
||||
[
|
||||
8.043511577329639,
|
||||
47.393493648131596
|
||||
],
|
||||
[
|
||||
8.043841204472528,
|
||||
47.393627534507715
|
||||
],
|
||||
[
|
||||
8.044368607901589,
|
||||
47.39304735775431
|
||||
],
|
||||
[
|
||||
8.04623100125653,
|
||||
47.39356059136196
|
||||
],
|
||||
[
|
||||
8.047516547113759,
|
||||
47.39120636999834
|
||||
],
|
||||
[
|
||||
8.04857135396935,
|
||||
47.39139605049661
|
||||
],
|
||||
[
|
||||
8.049543754040798,
|
||||
47.3916973063549
|
||||
],
|
||||
[
|
||||
8.05041726596852,
|
||||
47.39192045773251
|
||||
],
|
||||
[
|
||||
8.05003819475445,
|
||||
47.39296925655
|
||||
],
|
||||
[
|
||||
8.049922825254868,
|
||||
47.393962248959525
|
||||
],
|
||||
[
|
||||
8.049774493041156,
|
||||
47.39416307661054
|
||||
],
|
||||
[
|
||||
8.04880209296968,
|
||||
47.394107291228494
|
||||
],
|
||||
[
|
||||
8.047219882685113,
|
||||
47.393817206291374
|
||||
],
|
||||
[
|
||||
8.046263963970716,
|
||||
47.393605220135385
|
||||
],
|
||||
[
|
||||
8.04657710975647,
|
||||
47.39276842434944
|
||||
],
|
||||
[
|
||||
8.04708803182848,
|
||||
47.39186466997646
|
||||
],
|
||||
[
|
||||
8.0474835843996,
|
||||
47.391184054600785
|
||||
],
|
||||
[
|
||||
8.048258208184848,
|
||||
47.39134026218545
|
||||
],
|
||||
[
|
||||
8.049016350612987,
|
||||
47.391552257452844
|
||||
],
|
||||
[
|
||||
8.049856899826551,
|
||||
47.391753094288106
|
||||
],
|
||||
[
|
||||
8.050433747325599,
|
||||
47.39200971801864
|
||||
],
|
||||
[
|
||||
8.050153564255254,
|
||||
47.39256759138394
|
||||
],
|
||||
[
|
||||
8.049972269326105,
|
||||
47.393393233126204
|
||||
],
|
||||
[
|
||||
8.049856899826551,
|
||||
47.39391762048891
|
||||
],
|
||||
[
|
||||
8.049560235397877,
|
||||
47.39415191953893
|
||||
],
|
||||
[
|
||||
8.0485548726123,
|
||||
47.39416307661054
|
||||
],
|
||||
[
|
||||
8.047153957256796,
|
||||
47.393850677712095
|
||||
],
|
||||
[
|
||||
8.046263963970716,
|
||||
47.39361637732273
|
||||
],
|
||||
[
|
||||
8.045588228329194,
|
||||
47.39344901926367
|
||||
],
|
||||
[
|
||||
8.04600026225745,
|
||||
47.39275726698256
|
||||
],
|
||||
[
|
||||
8.045720079185827,
|
||||
47.39214360816524
|
||||
],
|
||||
[
|
||||
8.044599346900696,
|
||||
47.391753094288106
|
||||
]
|
||||
],
|
||||
"type": "LineString"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
53
src/lap_detection/lap_detector.py
Normal file
53
src/lap_detection/lap_detector.py
Normal file
@ -0,0 +1,53 @@
|
||||
from haversine import haversine, Unit
|
||||
|
||||
from lap_detection.api import Coordinate
|
||||
|
||||
|
||||
def dist(a: Coordinate, b: Coordinate):
|
||||
return haversine((a.lat, a.lon), (b.lat, b.lon), Unit.METERS)
|
||||
|
||||
|
||||
def count_laps(coords: list[Coordinate], start_coord: Coordinate, radius: int) -> int:
|
||||
"""Count the number of laps completed
|
||||
by tracking the number of times that the starting coordinate was passed by the given coordinate list
|
||||
Args:
|
||||
coords (list[Coordinate]): the list of coordinates
|
||||
start_coord (Coordinate): the coordinate to compare to
|
||||
radius (int): the radius around the starting coordinate
|
||||
Returns:
|
||||
int: the number of laps completed
|
||||
"""
|
||||
if not coords:
|
||||
return 0
|
||||
|
||||
laps = 0
|
||||
|
||||
distance_since_last = 0
|
||||
|
||||
# Do we start inside or outside the start/finish area?
|
||||
d_to_first = dist(coords[0], start_coord)
|
||||
inside = d_to_first < radius
|
||||
|
||||
for i in range(1, len(coords)):
|
||||
prev = coords[i - 1]
|
||||
curr = coords[i]
|
||||
|
||||
# Don't compare the starting coordinate with itself
|
||||
if curr == start_coord:
|
||||
continue
|
||||
|
||||
d = dist(prev, curr)
|
||||
distance_since_last += d
|
||||
|
||||
# Did we enter the start/finish area?
|
||||
d_to_start = dist(curr, start_coord)
|
||||
now_inside = d_to_start < radius
|
||||
if not inside and now_inside:
|
||||
# Ignore possible gps stutters with minimum lap distance threshold
|
||||
if distance_since_last > 100:
|
||||
laps += 1
|
||||
distance_since_last = 0
|
||||
|
||||
inside = now_inside
|
||||
|
||||
return laps
|
||||
131
src/lap_detection/main.py
Normal file
131
src/lap_detection/main.py
Normal file
@ -0,0 +1,131 @@
|
||||
# TASK: Rundenzählung Stoppuhr
|
||||
|
||||
# Hintergrund: Sportuhren zeichnen oft mittels GPS alle x Sekunden die Koordination (lat/lon) des jeweiligen Standorts auf.
|
||||
# Ein automatischer Modus detektiert, wenn der User eine Runde (Lap) gelaufen/gefahren ist und nimmt
|
||||
# automatisch die Zwischenzeit.
|
||||
#
|
||||
# Aufgabe: Unsere Aufgabe ist es nun diese Rundendetektion selbst in Python zu schreiben. Der Programmierer bekommt
|
||||
# eine .json-File mit Koordinaten. Diese sind immer als Liste [lon, lat], also z.B. [[8.5417, 47.3769],
|
||||
# [8.5434, 47.3772], [8.5446, 47.3783], ...]. Unser Programm soll als Antwort einen Integer mit der
|
||||
# ermittelten Anzahl Runden zurückgeben, also z.B. 2.
|
||||
#
|
||||
# - Die Aufgabe ist bewusst so offen gestellt, damit eure Kreativität nicht eingeschränkt ist. Ihr könnt den
|
||||
# Schwierigkeitsgrad selbst steuern, wie stark ihr ins Detail wollt, was ihr berücksichtigen wollt und was
|
||||
# nicht etc. Es geht damit auch nicht so sehr um die technischen Details, sondern darum, denn Code mithilfe
|
||||
# von Hints und Docstrings möglichst aussagekräftig und selbsterklärend zu machen. Nutzt auch 2-3 Tests mit
|
||||
# Pytest, um eueren Code 'sicherer' zu machen und zu validieren.
|
||||
#
|
||||
# Denkanstösse: - Was macht eine Runde aus?
|
||||
# - Wie entscheide ich, was eine Runde ist (geometrisch, Randbedingungen) -> 'Kochrezept' machen
|
||||
# - Wo schränke ich mich ein, wo mache ich Annahmen, ...
|
||||
#
|
||||
# Ziel: - Ein anderer User/Programmierer kann eure Funktionen und die darin hints/docstrings/tests lesen und
|
||||
# versteht was in eurem Code passiert, ohne den eigentlichen Code anschauen zu müssen.
|
||||
# - Es muss keine Perfektion angestrebt werden, es geht um die praktische Umsetzung der Theorie in einer
|
||||
# Praxisanwendung! Macht Annahmen/Vereinfachungen!
|
||||
# - Diejenigen, welche das Beispiel abgeben wollen, machen am besten einen
|
||||
# - neuen Branch (Aufgabe Rundenzählung)
|
||||
# - main-Funktion mit der Hauptfunktion, allfällige Helferfunktionen in einem separaten Modul
|
||||
# - test-Ordner mit 2-3 Pytests
|
||||
# - ein kurzer Readme mit Erklärung der Funktionsweise eures Rundenzählers und allfällige
|
||||
# Einschränkungen/Annahmen
|
||||
# - alle Funktionen mit sauberen Typehints und docstrings (Validation falls nötig)
|
||||
# -> commit und PR
|
||||
#
|
||||
# Hilfsmittel: - für benötigte geometrische Berechnungen dürfen auch KI-Hilfsmittel (Prompt Engineering) genutzt werden!
|
||||
# - Bitte nützt aber die KI nicht, um das ganze Programm zu erstellen, dokumentieren und testen :-)
|
||||
#
|
||||
# Hints: - für eine allfällige Distanz zwischen 2 Punkten -> haversine
|
||||
#
|
||||
# Usecases: - Im Ordner 'data' befinden sich 4 usecases mit Koordinaten.
|
||||
# - Lösungen für die usecases:
|
||||
# - usecase1: 3
|
||||
# - usecase2: 3 (3 3/4)
|
||||
# - usecase3: 0
|
||||
# - usecase4: 2
|
||||
# - Schreibt aber auch noch 1-2 eigene Testcases für allfällige wichtige Hilfsfunktionen.
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
from dacite import from_dict
|
||||
|
||||
from lap_detection.api import Collection, Coordinate
|
||||
from lap_detection.lap_detector import count_laps, dist
|
||||
|
||||
|
||||
def _check_path(path: str) -> Path:
|
||||
"""Checks the existence of the provided path
|
||||
Args:
|
||||
path (str): The path
|
||||
Returns:
|
||||
a Path object
|
||||
Raises:
|
||||
ValueError: If the path is not present
|
||||
"""
|
||||
path = Path(path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"{path} does not exist")
|
||||
return path
|
||||
|
||||
|
||||
def _read_file(path: Path) -> Collection:
|
||||
"""Reads the provided file
|
||||
Args:
|
||||
path (str): The path of the file
|
||||
Returns:
|
||||
an object of type Collection
|
||||
"""
|
||||
with path.open("r", encoding="utf-8") as f:
|
||||
loaded_file = json.load(f)
|
||||
collection = from_dict(data_class=Collection, data=loaded_file)
|
||||
return collection
|
||||
|
||||
|
||||
def _build_coordinates(collection: Collection) -> list[Coordinate]:
|
||||
"""Build a list of Coordinate objects from a Collection
|
||||
Args:
|
||||
collection (Collection): the feature collection
|
||||
Returns:
|
||||
a list of Coordinate objects
|
||||
"""
|
||||
return [
|
||||
Coordinate(coordinate[0], coordinate[1])
|
||||
for coordinate in collection.features[0].geometry.coordinates
|
||||
]
|
||||
|
||||
|
||||
def get_coordinates(path: str) -> list[Coordinate]:
|
||||
"""Validate the path, read the file and extract the coordinates
|
||||
Args:
|
||||
path (str): The path of the file
|
||||
Returns:
|
||||
a list of Coordinate objects
|
||||
"""
|
||||
validated_path = _check_path(path)
|
||||
data = _read_file(validated_path)
|
||||
coordinates = _build_coordinates(data)
|
||||
|
||||
return coordinates
|
||||
|
||||
|
||||
def main(path: str) -> None:
|
||||
coords = get_coordinates(path=path)
|
||||
|
||||
# detect laps -> business logic (build functions)
|
||||
|
||||
# Set the radius of the start/finish area based of the minimum distance of two following points
|
||||
radius = min([dist(coords[i - 1], coords[i]) for i in range(len(coords))])
|
||||
|
||||
# Count laps from each coordinate
|
||||
# The maximum indicates that the starting point is in a high traffic area
|
||||
n_rounds = max([count_laps(coords, start, radius) for start in coords])
|
||||
print(f"Anzahl Runden: {n_rounds}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main("data/usecase1.json")
|
||||
main("data/usecase2.json")
|
||||
main("data/usecase3.json")
|
||||
main("data/usecase4.json")
|
||||
57
tests/lap_detection/test_main.py
Normal file
57
tests/lap_detection/test_main.py
Normal file
@ -0,0 +1,57 @@
|
||||
import pytest
|
||||
|
||||
from lap_detection.api import Collection, Feature, Geometry, Coordinate
|
||||
import lap_detection.main as main
|
||||
|
||||
|
||||
# arrange
|
||||
@pytest.fixture
|
||||
def sample_collection():
|
||||
return Collection(
|
||||
"FeatureCollection",
|
||||
[
|
||||
Feature(
|
||||
"Feature",
|
||||
{},
|
||||
Geometry([[123.456, 654.321], [987.654, 101.010]], "LineString"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# arrange
|
||||
@pytest.mark.parametrize(
|
||||
"path,expected_laps",
|
||||
[
|
||||
("src/lap_detection/data/usecase1.json", 3),
|
||||
("src/lap_detection/data/usecase2.json", 3),
|
||||
("src/lap_detection/data/usecase3.json", 0),
|
||||
("src/lap_detection/data/usecase4.json", 2),
|
||||
],
|
||||
)
|
||||
def test_main(path, expected_laps, capsys):
|
||||
# act
|
||||
main.main(path)
|
||||
|
||||
# assert
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out.strip() == f"Anzahl Runden: {expected_laps}"
|
||||
|
||||
|
||||
def test_check_invalid_path():
|
||||
# assert
|
||||
with pytest.raises(FileNotFoundError, match="invalid_path.json does not exist"):
|
||||
# act
|
||||
main._check_path("invalid_path.json")
|
||||
|
||||
|
||||
def test_build_coordinates(sample_collection):
|
||||
# act
|
||||
coordinates = main._build_coordinates(sample_collection)
|
||||
|
||||
# assert
|
||||
assert isinstance(coordinates, list)
|
||||
assert len(coordinates) == 2
|
||||
assert isinstance(coordinates[1], Coordinate)
|
||||
assert coordinates[1].lon == 987.654
|
||||
assert coordinates[1].lat == 101.010
|
||||
54
tests/test_lap_detector.py
Normal file
54
tests/test_lap_detector.py
Normal file
@ -0,0 +1,54 @@
|
||||
from lap_detection.api import Coordinate
|
||||
from lap_detection.lap_detector import count_laps
|
||||
|
||||
|
||||
def test_no_laps():
|
||||
# arrange
|
||||
start = Coordinate(lat=47.0, lon=8.0)
|
||||
coords = [
|
||||
Coordinate(lat=47.0001, lon=8.0001),
|
||||
Coordinate(lat=47.0002, lon=8.0002),
|
||||
Coordinate(lat=47.0003, lon=8.0003),
|
||||
]
|
||||
|
||||
# act
|
||||
result = count_laps(coords, start, radius=20)
|
||||
|
||||
# assert
|
||||
assert result == 0
|
||||
|
||||
|
||||
def test_single_lap():
|
||||
# arrange
|
||||
start = Coordinate(lat=47.0, lon=8.0)
|
||||
coords = [
|
||||
Coordinate(lat=47.0030, lon=8.0000), # far from start
|
||||
Coordinate(lat=47.0020, lon=8.0010),
|
||||
Coordinate(lat=47.0010, lon=8.0020),
|
||||
Coordinate(lat=47.0001, lon=8.0001), # back near start -> lap counted
|
||||
Coordinate(lat=47.0030, lon=8.0000), # far again -> no second lap
|
||||
]
|
||||
|
||||
# act
|
||||
result = count_laps(coords, start, radius=20)
|
||||
|
||||
# assert
|
||||
assert result == 1
|
||||
|
||||
|
||||
def test_two_laps():
|
||||
# arrange
|
||||
start = Coordinate(lat=47.0, lon=8.0)
|
||||
coords = [
|
||||
Coordinate(lat=47.0030, lon=8.0000), # far from start
|
||||
Coordinate(lat=47.0001, lon=8.0001), # back near start -> lap 1
|
||||
Coordinate(lat=47.0002, lon=8.0002), # still near start -> don't count twice
|
||||
Coordinate(lat=47.0030, lon=8.0000), # far again
|
||||
Coordinate(lat=47.0001, lon=8.0001), # back near start -> lap 2
|
||||
]
|
||||
|
||||
# act
|
||||
result = count_laps(coords, start, radius=20)
|
||||
|
||||
# assert
|
||||
assert result == 2
|
||||
Loading…
x
Reference in New Issue
Block a user