Add tiles management

This commit is contained in:
Arnaud Delcasse 2023-03-29 12:50:25 +02:00
parent 77c8576254
commit bbc682386a
18 changed files with 559 additions and 64 deletions

View File

@ -14,9 +14,15 @@ Trips and journeys are managed through the provided gRPC service and stored in t
### Tiles architecture and cache
To reduce dependencies on the chosen database, we decided to manage geographical calculations directly in out code instead of relying completely on MongoDB geographical features.
To reduce dependencies on the chosen database, we decided to manage geographical calculations directly in our code instead of relying completely on MongoDB geographical features.
For opti
We use a tiles system to retrieve relevant journeys easily. Each tile is defined by :
- driver or passenger status of the journey
- date of the journey
- Tile ID in the tiles grid. The tiles grid is inspired by Valhalla https://github.com/Telenav/open-source-spec/blob/master/valhalla/doc/valhalla-tiles-basic.md. We use a level 0 grid
Journeys are stored in each relevant tile (where a departure, destination or part of the polyline enters the tile)
### Search

29
go.mod
View File

@ -3,16 +3,27 @@ module git.coopgo.io/coopgo-platform/carpool-service
go 1.18
replace git.coopgo.io/coopgo-platform/routing-service => ../../coopgo-platform/routing-service/
replace git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss => ./interoperability/ocss/
require git.coopgo.io/coopgo-platform/routing-service v0.0.0
require git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0
require (
git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss v0.0.0
github.com/google/uuid v1.3.0
github.com/paulmach/orb v0.9.0
github.com/rs/zerolog v1.29.0
github.com/spf13/viper v1.15.0
go.mongodb.org/mongo-driver v1.11.3
google.golang.org/grpc v1.53.0
google.golang.org/protobuf v1.30.0
)
require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/schema v1.2.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
@ -20,30 +31,24 @@ require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/paulmach/orb v0.9.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.29.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/twpayne/go-polyline v1.1.1 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

33
go.sum
View File

@ -48,6 +48,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -56,6 +57,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -105,6 +107,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -125,6 +128,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -139,8 +144,10 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
@ -159,9 +166,11 @@ github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
@ -187,9 +196,11 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/twpayne/go-polyline v1.1.1 h1:/tSF1BR7rN4HWj4XKqvRUNrCiYVMCvywxTFVofvDV0w=
github.com/twpayne/go-polyline v1.1.1/go.mod h1:ybd9IWWivW/rlXPXuuckeKUyF3yrIim+iqA7kSl4NFY=
@ -221,8 +232,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -288,8 +300,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -350,10 +362,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -363,10 +373,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -518,11 +526,10 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=

View File

@ -2,17 +2,23 @@ package handler
import (
"git.coopgo.io/coopgo-platform/carpool-service/storage"
"git.coopgo.io/coopgo-platform/carpool-service/tiles"
"git.coopgo.io/coopgo-platform/routing-service"
"github.com/spf13/viper"
)
type CarpoolServiceHandler struct {
Config *viper.Viper
Storage storage.Storage
Tiles *tiles.TilesHandler
Routing routing.RoutingService
}
func NewCarpoolServiceHandler(cfg *viper.Viper, storage storage.Storage) (*CarpoolServiceHandler, error) {
func NewCarpoolServiceHandler(cfg *viper.Viper, storage storage.Storage, tilesHandler *tiles.TilesHandler, routing routing.RoutingService) (*CarpoolServiceHandler, error) {
return &CarpoolServiceHandler{
Config: cfg,
Storage: storage,
Tiles: tilesHandler,
Routing: routing,
}, nil
}

View File

@ -4,9 +4,13 @@ import (
"errors"
"time"
"git.coopgo.io/coopgo-platform/carpool-service/helpers"
"git.coopgo.io/coopgo-platform/carpool-service/tiles"
"git.coopgo.io/coopgo-platform/routing-service/encoding/polylines"
"github.com/google/uuid"
"github.com/paulmach/orb"
"github.com/paulmach/orb/geojson"
"github.com/paulmach/orb/simplify"
"github.com/rs/zerolog/log"
)
@ -26,10 +30,23 @@ func (h *CarpoolServiceHandler) CreateRegularRoutes(routes []*geojson.FeatureCol
return errors.New("no polyline found in properties from feature collection")
}
lineString := geojson.NewFeature(polylines.Decode(&polyline, 5))
lineString.Properties["encoded_polyline"] = polyline
linestring := polylines.Decode(&polyline, 5)
simplified_linestring := simplify.DouglasPeucker(0.003).Simplify(linestring.Clone())
r.Append(lineString)
// Simplification tests
// Antibes -> Rennes
// Without Simplify : 12230
// 0.01 -> 129
// 0.005 -> 230
// 0.003 -> 338
lsFeature := geojson.NewFeature(simplified_linestring)
lsFeature.Properties["encoded_polyline"] = polyline
r.Append(lsFeature)
gridids := tiles.LineStringGridIds(simplified_linestring.(orb.LineString))
r.ExtraMembers["grid_ids"] = gridids
}
@ -40,18 +57,18 @@ func (h *CarpoolServiceHandler) CreateRegularRoutes(routes []*geojson.FeatureCol
return nil
}
func (h *CarpoolServiceHandler) GetUserPlanning(userid string, minDepartureDate time.Time, maxDepartureDate time.Time) (map[string][]PlannedRouteSchedule, error) {
func (h *CarpoolServiceHandler) GetUserPlanning(userid string, minDepartureDate time.Time, maxDepartureDate time.Time) (map[string][]helpers.PlannedRouteSchedule, error) {
log.Debug().
Str("user_id", userid).
Time("min_departure_date", minDepartureDate).
Time("max_departure_date", maxDepartureDate).
Msg("carpool service handler - GetUserPlanning")
results := map[string][]PlannedRouteSchedule{}
results := map[string][]helpers.PlannedRouteSchedule{}
current_date := minDepartureDate
for current_date.Before(maxDepartureDate) {
results[current_date.Format("2006-01-02")] = []PlannedRouteSchedule{}
results[current_date.Format("2006-01-02")] = []helpers.PlannedRouteSchedule{}
current_date = current_date.Add(24 * time.Hour)
}
@ -62,7 +79,7 @@ func (h *CarpoolServiceHandler) GetUserPlanning(userid string, minDepartureDate
}
for _, r := range routes {
rr := RegularRoute(*r)
rr := helpers.RegularRoute(*r)
schedules, err := rr.PlannedJourneySchedules(minDepartureDate, maxDepartureDate)
if err != nil {
log.Error().Err(err)

View File

@ -3,10 +3,116 @@ package handler
import (
"time"
"git.coopgo.io/coopgo-platform/carpool-service/interoperability/ocss"
"git.coopgo.io/coopgo-platform/routing-service"
"github.com/paulmach/orb"
"github.com/paulmach/orb/geojson"
"github.com/paulmach/orb/planar"
"github.com/rs/zerolog/log"
)
func (h *CarpoolServiceHandler) GetDriverJourneys(departure orb.Point, arrival orb.Point, minDate time.Time, maxDate time.Time) ([]ocss.DriverJourney, error) {
return nil, nil
type SearchResult struct {
ID string
Route *geojson.FeatureCollection
DepartureDate time.Time
Itinerary *routing.Route
}
func (h *CarpoolServiceHandler) GetDriverJourneys(departure orb.Point, arrival orb.Point, departureRadius *float64, arrivalRadius *float64, minDate time.Time, maxDate time.Time, count *int64) ([]SearchResult, error) {
log.Debug().
Any("departure", departure).
Any("arrival", arrival).
Any("dep radius", departureRadius).
Any("arr radius", arrivalRadius).
Str("mindate", minDate.Format(time.RFC3339)).
Str("maxdate", maxDate.Format(time.RFC3339)).
Any("count", count).
Msg("Carpool service handler - GetDriverJourneys")
if count == nil {
c := int64(10)
count = &c
}
if departureRadius == nil {
d := float64(1000)
departureRadius = &d
}
if arrivalRadius == nil {
a := float64(1000)
departureRadius = &a
}
tileset, err := h.Tiles.GetTiles("driver", minDate, departure, arrival)
if err != nil {
log.Error().
Str("date", minDate.Format(time.RFC3339)).
Any("departure", departure).
Any("arrival", arrival).
Err(err).
Msg("could not retrieve tiles")
return nil, err
}
log.Debug().Any("tileset", tileset).Msg("got tileset")
candidate_routes := tileset.GetTiledRoutes()
journeys := []SearchResult{}
counted := int64(0)
for _, r := range candidate_routes {
ls := r.Route.Features[2].Geometry
distanceFromDeparture, indexDeparture := planar.DistanceFromWithIndex(ls, departure)
distanceFromArrival, indexArrival := planar.DistanceFromWithIndex(ls, arrival)
if indexArrival >= indexDeparture && distanceFromDeparture <= *departureRadius && distanceFromArrival < *arrivalRadius {
routePoints := []orb.Point{r.Route.Features[0].Point(), departure, arrival, r.Route.Features[0].Point()}
itinerary, err := h.Routing.Route(routePoints)
if err != nil {
log.Error().Err(err).Msg("error getting route with viapoints")
continue
}
journeys = append(journeys, SearchResult{
ID: r.ID,
Route: r.Route,
DepartureDate: r.DepartureDate,
Itinerary: itinerary,
})
counted = counted + 1
if counted == *count {
break
}
}
}
return journeys, nil
}
// //distance to Linestring computes the distance from point to the given linestring anf returns this minimum distance and the segment number
// func distanceToLineSTring(point orb.Point, lineString orb.LineString) (distance float64, closest_segment_index int) {
// minDistance := math.Inf(1)
// closest := -1
// for i := 0; i < len(lineString)-1; i++ {
// segment1 := lineString[i]
// segment2 := lineString[i+1]
// dist := distanceToSegment(point, segment1, segment2)
// if dist < minDistance {
// minDistance = dist
// closest = i
// }
// }
// return minDistance, closest
// }
// //distanceToSegment computes the distance to the segment defined with a starting and a ending point
// func distanceToSegment(point orb.Point, start orb.Point, end orb.Point) float64 {
// len := (end.Lon()-start.Lon())*(end.Lon()-start.Lon()) + (end.Lat()-start.Lat())*(end.Lat()-start.Lat())
// s := ((start.Lat()-point.Lat())*(end.Lon()-start.Lon()) - (start.Lon()-point.Lon())*(end.Lat()-start.Lat())) / len
// distance := math.Abs(s) * math.Sqrt(len)
// return distance
// }

8
helpers/journeys.go Normal file
View File

@ -0,0 +1,8 @@
package helpers
import "time"
type PlannedJourney struct {
Route RegularRoute
DepartureDate time.Time
}

View File

@ -1,4 +1,4 @@
package handler
package helpers
import (
"errors"

29
main.go
View File

@ -8,6 +8,8 @@ import (
grpcserver "git.coopgo.io/coopgo-platform/carpool-service/servers/grpc/server"
ocssapi "git.coopgo.io/coopgo-platform/carpool-service/servers/ocss-api"
"git.coopgo.io/coopgo-platform/carpool-service/storage"
"git.coopgo.io/coopgo-platform/carpool-service/tiles"
"git.coopgo.io/coopgo-platform/routing-service"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
@ -21,10 +23,12 @@ func main() {
}
var (
service_name = cfg.GetString("name")
grpc_enable = cfg.GetBool("services.grpc.enable")
ocss_enable = cfg.GetBool("services.ocss_api.enable")
dev_env = cfg.GetBool("dev_env")
service_name = cfg.GetString("name")
grpc_enable = cfg.GetBool("services.grpc.enable")
ocss_enable = cfg.GetBool("services.ocss_api.enable")
dev_env = cfg.GetBool("dev_env")
routing_service_type = cfg.GetString("routing.type")
valhalla_base_url = cfg.GetString("routing.valhalla.base_url")
)
if dev_env {
@ -36,11 +40,24 @@ func main() {
storage, err := storage.NewStorage(cfg)
if err != nil {
log.Fatal().Err(err).Msg("could not initiate storage")
return
}
handler, err := handler.NewCarpoolServiceHandler(cfg, storage)
routing, err := routing.NewRoutingService(routing_service_type, valhalla_base_url)
if err != nil {
log.Fatal().Err(err).Msg("could not initiate incentives handler")
log.Fatal().Err(err).Msg("Could not initiate the routing service")
return
}
tilesHandler, err := tiles.NewTilesHandler(cfg, storage)
if err != nil {
log.Fatal().Err(err).Msg("could not initialize tiles handler")
return
}
handler, err := handler.NewCarpoolServiceHandler(cfg, storage, tilesHandler, routing)
if err != nil {
log.Fatal().Err(err).Msg("could not initiate carpoool service handler")
return
}

View File

@ -37,10 +37,15 @@ func Run(done chan error, cfg *viper.Viper, handler *handler.CarpoolServiceHandl
return
}
err = http.ListenAndServe(address, ocss.NewServer(service))
server := ocss.NewServer(service)
server.AddOperator("mobilaude_preprod", "wxvSwUpTTZ5wiAxrHpPfDsijz88w6tMW")
server.AuthorizedOperators = append(server.AuthorizedOperators, ocss.AuthorizedOperator{
Operator: "mobilaude",
ApiKey: "$2y$10$TJuDZDu.mqy5dDKGMSfxSO5f6pz/36XVrAyQ1CXJd63ccjRlX7gpC",
})
srv := &http.Server{
Handler: ocss.NewServer(service),
Handler: server,
Addr: address,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,

View File

@ -21,12 +21,64 @@ func (s *OCSSApiService) GetDriverJourneys(ctx context.Context, departureLat flo
minDate := departureDate.Add(-td * time.Second)
maxDate := departureDate.Add(td * time.Second)
result, err := s.Handler.GetDriverJourneys(departure, arrival, minDate, maxDate)
journeys, err := s.Handler.GetDriverJourneys(departure, arrival, departureRadius, arrivalRadius, minDate, maxDate, count)
if err != nil {
return nil, err
}
return result, nil
results := []ocss.DriverJourney{}
for _, j := range journeys {
usermap := j.Route.ExtraMembers["user"].(map[string]any)
journeyId := j.Route.ExtraMembers.MustString("journey_id")
driverDepartureLat := j.Route.Features[0].Point().Lat()
driverDepartureLng := j.Route.Features[0].Point().Lon()
driverArrivalLat := j.Route.Features[1].Point().Lat()
driverArrivalLng := j.Route.Features[1].Point().Lon()
driverDepartureDate := ocss.JSONTime(j.DepartureDate)
duration := time.Duration(0)
var distance *int64
if len(j.Itinerary.Legs) > 2 {
duration = j.Itinerary.Legs[1].Duration
dist := int64(j.Itinerary.Legs[1].Distance)
distance = &dist
}
results = append(results, ocss.DriverJourney{
DriverTrip: ocss.DriverTrip{
Driver: ocss.User{
ID: usermap["id"].(string),
Operator: usermap["opeator"].(string),
Alias: usermap["alias"].(string),
},
Trip: ocss.Trip{
Operator: "ridygo.fr",
PassengerPickupLat: departureLat,
PassengerPickupLng: departureLng,
PassengerDropLat: arrivalLat,
PassengerDropLng: arrivalLng,
DriverDepartureLat: &driverDepartureLat,
DriverDepartureLng: &driverDepartureLng,
DriverArrivalLat: &driverArrivalLat,
DriverArrivalLng: &driverArrivalLng,
Duration: duration,
Distance: distance,
},
},
JourneySchedule: ocss.JourneySchedule{
ID: &journeyId,
PassengerPickupDate: ocss.JSONTime(j.DepartureDate.Add(j.Itinerary.Legs[0].Duration)),
DriverDepartureDate: &driverDepartureDate,
Type: ocss.Planned,
},
// Price: &ocss.Price{
// Amount: ,
// Currency: "EUR",
// },
})
}
return results, nil
}
func (s *OCSSApiService) GetPassengerJourneys(ctx context.Context, departureLat float64, departureLng float64, arrivalLat float64, arrivalLng float64, departureDate time.Time, timeDelta *time.Duration, departureRadius *float64, arrivalRadius *float64, count *int64) ([]ocss.PassengerJourney, error) {

View File

@ -115,6 +115,92 @@ func (s MongoDBStorage) GetUserRegularRoutes(userid string) ([]*geojson.FeatureC
return results, nil
}
func (s MongoDBStorage) GetDriverRegularRoutesForTile(day string, gridId int64) ([]*geojson.FeatureCollection, error) {
findOptions := options.Find()
collection := s.Client.Database(s.DbName).Collection(s.Collections["regular_routes"])
cur, err := collection.Find(context.TODO(), bson.M{
"properties.schedule.day": day,
"grid_ids": gridId,
"properties.is_driver": true,
}, findOptions)
if err != nil {
return nil, err
}
results := []*geojson.FeatureCollection{}
for cur.Next(context.TODO()) {
var elem bson.M
if err := cur.Decode(&elem); err != nil {
return nil, err
}
bsonBytes, _ := bson.Marshal(elem)
fc := geojson.NewFeatureCollection()
err := fc.UnmarshalBSON(bsonBytes)
if err != nil {
return nil, err
}
fc.ExtraMembers["id"] = fc.ExtraMembers.MustString("_id")
delete(fc.ExtraMembers, "_id")
for k, v := range fc.ExtraMembers {
if val, ok := v.(primitive.D); ok {
em := map[string]any{}
jsonbytes, _ := bson.MarshalExtJSON(val, true, true)
json.Unmarshal(jsonbytes, &em)
fc.ExtraMembers[k] = em
}
}
results = append(results, fc)
}
return results, nil
}
func (s MongoDBStorage) GetPassengerRegularRoutesForTile(day string, gridId int64) ([]*geojson.FeatureCollection, error) {
findOptions := options.Find()
collection := s.Client.Database(s.DbName).Collection(s.Collections["regular_routes"])
cur, err := collection.Find(context.TODO(), bson.M{
"properties.schedule.day": day,
"grid_ids": gridId,
"properties.is_passenger": true,
}, findOptions)
if err != nil {
return nil, err
}
results := []*geojson.FeatureCollection{}
for cur.Next(context.TODO()) {
var elem bson.M
if err := cur.Decode(&elem); err != nil {
return nil, err
}
bsonBytes, _ := bson.Marshal(elem)
fc := geojson.NewFeatureCollection()
err := fc.UnmarshalBSON(bsonBytes)
if err != nil {
return nil, err
}
fc.ExtraMembers["id"] = fc.ExtraMembers.MustString("_id")
delete(fc.ExtraMembers, "_id")
for k, v := range fc.ExtraMembers {
if val, ok := v.(primitive.D); ok {
em := map[string]any{}
jsonbytes, _ := bson.MarshalExtJSON(val, true, true)
json.Unmarshal(jsonbytes, &em)
fc.ExtraMembers[k] = em
}
}
results = append(results, fc)
}
return results, nil
}
// func (s MongoDBStorage) CreatePassengerRegularTrips(trips []*geojson.FeatureCollection) error {
// log.Debug().Msg("Storage - CreatePassengerRegularTrips")

View File

@ -10,6 +10,8 @@ import (
type Storage interface {
CreateRegularRoutes([]*geojson.FeatureCollection) error
GetUserRegularRoutes(userid string) ([]*geojson.FeatureCollection, error)
GetDriverRegularRoutesForTile(day string, gridId int64) ([]*geojson.FeatureCollection, error)
GetPassengerRegularRoutesForTile(day string, gridId int64) ([]*geojson.FeatureCollection, error)
}
func NewStorage(cfg *viper.Viper) (Storage, error) {

33
tiles/grid.go Normal file
View File

@ -0,0 +1,33 @@
package tiles
import "github.com/paulmach/orb"
// GridId defines the position of a tile
// It follows the Valhalla way of handling tiles : https://github.com/Telenav/open-source-spec/blob/master/valhalla/doc/valhalla-tiles-basic.md
type GridId uint64
const tilesize float64 = 1.0
// PointGridId returns the id on the grid for a given point
func PointGridId(point orb.Point) GridId {
width := int64(360 / tilesize)
return GridId(int64((point.Lat()+90)/tilesize)*width + int64((point.Lon()+180)/tilesize))
}
// LineStringGridIds returns the list of ids on the grid the linestring goes through.
// In some really specific cases on tile edges, this could be unaccurate if the polyline was too much simplified
func LineStringGridIds(linestring orb.LineString) []GridId {
results := []GridId{}
gidmap := map[int64]bool{}
for _, p := range linestring {
gid := PointGridId(p)
if _, ok := gidmap[int64(gid)]; !ok {
gidmap[int64(gid)] = true
results = append(results, gid)
}
}
return results
}

21
tiles/routes.go Normal file
View File

@ -0,0 +1,21 @@
package tiles
import (
"time"
"github.com/paulmach/orb/geojson"
)
type TiledRouteType int
const (
TiledRouteRegular TiledRouteType = iota
TiledRoutePunctual
)
type TiledRoute struct {
ID string
Type TiledRouteType
Route *geojson.FeatureCollection
DepartureDate time.Time
}

49
tiles/tiles-handler.go Normal file
View File

@ -0,0 +1,49 @@
package tiles
import (
"time"
"git.coopgo.io/coopgo-platform/carpool-service/storage"
"github.com/paulmach/orb"
"github.com/spf13/viper"
)
type TilesHandler struct {
Config *viper.Viper
PersistentStorage storage.Storage
CachedTileset map[string]Tileset
}
func NewTilesHandler(cfg *viper.Viper, persistantStorage storage.Storage) (*TilesHandler, error) {
return &TilesHandler{
Config: cfg,
PersistentStorage: persistantStorage,
}, nil
}
// GetTiles retrieves tiles
func (h *TilesHandler) GetTiles(driverOrPassenger string, date time.Time, points ...orb.Point) (*Tileset, error) {
result := Tileset{}
grid_ids := []GridId{}
nb_points := len(points)
if nb_points > 1 {
grid_ids = LineStringGridIds(orb.LineString(points))
} else if nb_points == 1 {
grid_ids = []GridId{PointGridId(points[0])}
}
dateString := date.Format("2006-01-02")
for _, gid := range grid_ids {
tile, err := h.GetTile(driverOrPassenger, date, gid)
if err != nil {
return nil, err
}
result[dateString] = tile
}
return &result, nil
}

View File

@ -2,26 +2,83 @@ package tiles
import (
"fmt"
"strings"
"time"
"github.com/paulmach/orb"
"git.coopgo.io/coopgo-platform/carpool-service/helpers"
"github.com/google/uuid"
"github.com/paulmach/orb/geojson"
"github.com/rs/zerolog/log"
)
type IndexedTilesets struct {
Tileset map[string]*Tile // Key is "date:geo" (example : "2023-01-01:fr-south-west")
ByDate map[string]Tileset // Key is the date in the form "YYYY-MM-DD"
ByGeo map[string]Tileset // Key is the Geo tiles id (could be any string as defined in the configuration)
}
type Tileset []*Tile
// Tiles are defined as :
// - "driver" or "passenger" string depending on the status of the journeys included in the tiles
// - a date formatted as "2006-01-02"
// - Bounds of the tile
type Tile struct {
Date string
GeoId string
Bounds orb.Bound
Journeys []*geojson.FeatureCollection
DriverOrPassenger string
Date string // Date formatted as "2006-01-02"
GridId GridId
TiledRoutes []TiledRoute
}
func (t Tile) Id() string {
return fmt.Sprintf("%s:%s", t.Date, t.GeoId)
func (tile *Tile) ID() string {
return fmt.Sprintf("%s/%s/%d", tile.DriverOrPassenger, tile.Date, tile.GridId)
}
// GetTile retrieves a tile from persistant storage or cache
func (h *TilesHandler) GetTile(driverOrPassenger string, date time.Time, gridid GridId) (*Tile, error) {
routes := []*geojson.FeatureCollection{}
day := strings.ToUpper(date.Format("Mon"))
dateString := date.Format("2006-01-02")
if driverOrPassenger == "driver" {
regroutes, err := h.PersistentStorage.GetDriverRegularRoutesForTile(day, int64(gridid))
if err != nil {
return nil, err
}
routes = regroutes
} else if driverOrPassenger == "passenger" {
regroutes, err := h.PersistentStorage.GetPassengerRegularRoutesForTile(day, int64(gridid))
if err != nil {
return nil, err
}
routes = regroutes
}
date0h, _ := time.Parse("2006-01-02", dateString)
date24h := date0h.Add(24 * time.Hour)
result := []TiledRoute{}
for _, r := range routes {
rr := helpers.RegularRoute(*r)
schedules, err := rr.PlannedJourneySchedules(date0h, date24h)
if err != nil {
log.Error().Err(err)
return nil, err
}
if len(schedules) > 0 {
tiledRoute := TiledRoute{
ID: uuid.NewString(),
Route: r,
Type: TiledRouteRegular,
DepartureDate: schedules[0].DepartureDate,
}
result = append(result, tiledRoute)
}
}
return &Tile{
DriverOrPassenger: driverOrPassenger,
Date: dateString,
GridId: gridid,
TiledRoutes: result,
}, nil
}

18
tiles/tilesets.go Normal file
View File

@ -0,0 +1,18 @@
package tiles
// Tileset stores tiles by TileID
type Tileset map[string]*Tile
func (tileset Tileset) GetTiledRoutes() []TiledRoute {
idmap := map[string]bool{}
result := []TiledRoute{}
for _, tile := range tileset {
for _, tr := range tile.TiledRoutes {
if _, ok := idmap[tr.Route.ExtraMembers.MustString("id")]; !ok {
idmap[tr.Route.ExtraMembers.MustString("id")] = true
result = append(result, tr)
}
}
}
return result
}