├── images
├── 1.png
├── 2.png
├── 3.png
└── index.png
├── go.mod
├── model.go
├── .github
└── workflows
│ └── main.yml
├── .golangci.yml
├── utility.go
├── index_test.go
├── LICENSE
├── index.go
├── README.md
├── Makefile
├── go.sum
├── lookups.go
├── example
└── main.go
├── .gitignore
└── lookups_test.go
/images/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4lie/lookups/HEAD/images/1.png
--------------------------------------------------------------------------------
/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4lie/lookups/HEAD/images/2.png
--------------------------------------------------------------------------------
/images/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4lie/lookups/HEAD/images/3.png
--------------------------------------------------------------------------------
/images/index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/4lie/lookups/HEAD/images/index.png
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/4lie/lookups
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/davecgh/go-spew v1.1.1 // indirect
7 | github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35
8 | github.com/stretchr/testify v1.6.1
9 | )
10 |
--------------------------------------------------------------------------------
/model.go:
--------------------------------------------------------------------------------
1 | package lookups
2 |
3 | import "github.com/golang/geo/s2"
4 |
5 | type (
6 | Props map[string]interface{}
7 |
8 | Coordinate struct {
9 | Latitude float64 `json:"latitude"`
10 | Longitude float64 `json:"longitude"`
11 | SequenceID int `json:"sequence_id"`
12 | }
13 |
14 | PolyProps struct {
15 | Props Props `json:"props"`
16 | Polygon *s2.Polygon `json:"polygon"`
17 | }
18 |
19 | CoordinateProps struct {
20 | Props []Props `json:"props"`
21 | Coordinate Coordinate `json:"coordinate"`
22 | }
23 | )
24 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | push:
8 | branches:
9 | - master
10 |
11 | jobs:
12 |
13 | build:
14 | name: Build
15 | runs-on: ubuntu-20.04
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v2
19 | - name: Install go
20 | uses: actions/setup-go@v2
21 | with:
22 | go-version: 1.15
23 | - name: Install golangci-lint
24 | run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.30.0
25 | - name: Lint
26 | run: make lint
27 | - name: Test
28 | run: make test
29 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # This file contains all available configuration options
2 | # with their default values.
3 |
4 | # options for analysis running
5 | run:
6 | # default concurrency is a available CPU number
7 | concurrency: 4
8 |
9 | # timeout for analysis, e.g. 30s, 5m, default is 1m
10 | deadline: 10m
11 |
12 | # exit code when at least one issue was found, default is 1
13 | issues-exit-code: 1
14 |
15 | # include test files or not, default is true
16 | tests: true
17 |
18 | # all available settings of specific linters
19 | linters-settings:
20 | dupl:
21 | # tokens count to trigger issue, 150 by default
22 | threshold: 200
23 |
24 | linters:
25 | enable-all: true
26 |
27 | issues:
28 | # Excluding configuration per-path, per-linter, per-text and per-source
29 | exclude-rules:
30 | - path: \.go
31 | linters:
32 | - gci
33 | - nolintlint
34 |
--------------------------------------------------------------------------------
/utility.go:
--------------------------------------------------------------------------------
1 | package lookups
2 |
3 | import "github.com/golang/geo/s2"
4 |
5 | func PolyPropsFromCoordinates(coordinates [][]Coordinate, props map[string]interface{}) PolyProps {
6 | return PolyProps{
7 | Polygon: PolygonFromCoordinates(coordinates),
8 | Props: props,
9 | }
10 | }
11 |
12 | func PolygonFromCoordinates(coordinates [][]Coordinate) *s2.Polygon {
13 | loops := make([]*s2.Loop, 0, len(coordinates))
14 |
15 | for _, l := range coordinates {
16 | loop := LoopFromCoordinates(l)
17 | loop.Normalize()
18 | loops = append(loops, loop)
19 | }
20 |
21 | return s2.PolygonFromLoops(loops)
22 | }
23 |
24 | func LoopFromCoordinates(coordinates []Coordinate) *s2.Loop {
25 | points := make([]s2.Point, 0, len(coordinates))
26 |
27 | for _, p := range coordinates {
28 | points = append(points, s2.PointFromLatLng(s2.LatLngFromDegrees(p.Latitude, p.Longitude)))
29 | }
30 |
31 | return s2.LoopFromPoints(points)
32 | }
33 |
--------------------------------------------------------------------------------
/index_test.go:
--------------------------------------------------------------------------------
1 | package lookups_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/4lie/lookups"
7 |
8 | "github.com/golang/geo/s2"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func TestS2Index(t *testing.T) {
13 | a := assert.New(t)
14 |
15 | rawPolygon := [][]float64{
16 | {
17 | 35.837037430733666,
18 | 50.993492603302,
19 | },
20 | {
21 | 35.837037430733666,
22 | 50.994919538497925,
23 | },
24 | {
25 | 35.837837616204105,
26 | 50.994919538497925,
27 | },
28 | {
29 | 35.837837616204105,
30 | 50.993492603302,
31 | },
32 | }
33 |
34 | points := make([]s2.Point, 0, len(rawPolygon))
35 |
36 | for _, p := range rawPolygon {
37 | points = append(points, s2.PointFromLatLng(s2.LatLngFromDegrees(p[0], p[1])))
38 | }
39 |
40 | polygon := s2.PolygonFromLoops([]*s2.Loop{s2.LoopFromPoints(points)})
41 |
42 | s := lookups.NewS2Index(15)
43 |
44 | ids := s.Cover(polygon)
45 |
46 | a.Len(ids, 2)
47 | a.Contains(ids, "3f8dbf754")
48 | a.Contains(ids, "3f8dbf75c")
49 | }
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 4lie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/index.go:
--------------------------------------------------------------------------------
1 | package lookups
2 |
3 | import "github.com/golang/geo/s2"
4 |
5 | // GeoIndex is an interface for geo indexing.
6 | type GeoIndex interface {
7 | Find(Coordinate) string
8 | Cover(*s2.Polygon) []string
9 | }
10 |
11 | // S2Index is an s2-based polygon indexer.
12 | type S2Index struct {
13 | level int
14 | }
15 |
16 | // NewS2Index returns a new s2 indexer instance.
17 | func NewS2Index(level int) S2Index {
18 | return S2Index{level: level}
19 | }
20 |
21 | // Find returns an s2 cell that contains the given point.
22 | func (s S2Index) Find(coordinate Coordinate) string {
23 | ll := s2.LatLngFromDegrees(coordinate.Latitude, coordinate.Longitude)
24 |
25 | return s2.CellIDFromLatLng(ll).Parent(s.level).ToToken()
26 | }
27 |
28 | // Covers returns all S2 cells that cover the given polygon.
29 | func (s S2Index) Cover(polygon *s2.Polygon) []string {
30 | if polygon.NumEdges() < 1 {
31 | return []string{}
32 | }
33 |
34 | ids := s2.SimpleRegionCovering(polygon, polygon.Edge(0).V0, s.level)
35 |
36 | out := make([]string, 0, len(ids))
37 |
38 | for _, id := range ids {
39 | out = append(out, id.ToToken())
40 | }
41 |
42 | return out
43 | }
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Geo Lookups
2 |
3 | [](https://github.com/4lie/lookups/actions/workflows/main.yml)
4 | [](https://pkg.go.dev/github.com/4lie/lookups)
5 |
6 | ## Introduction
7 |
8 | Fast and in-memory geo lookup library.
9 |
10 | Simply add polygons and run queries.
11 | It uses Google's S2 Library for indexing and it's super fast :rocket:.
12 |
13 | ## Install
14 |
15 | ``` bash
16 | go get github.com/4lie/lookups
17 | ```
18 |
19 | ## Examples
20 |
21 | `lookups_test.go` contains several useful examples. You can check visualizations of them here.
22 |
23 | | Image | Description |
24 | |---------------------------------------------|----------------------------|
25 | |
| How indexing works |
26 | |
| Simple polygon example |
27 | |
| Polygon with holes example |
28 | |
| Multiple polygons example |
29 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #@IgnoreInspection BashAddShebang
2 |
3 | export APP=lookups
4 |
5 | export ROOT=$(realpath $(dir $(lastword $(MAKEFILE_LIST))))
6 |
7 | all: install format lint
8 |
9 | install:
10 | go mod download
11 |
12 | check-formatter:
13 | which gofumpt || GO111MODULE=off go get -u mvdan.cc/gofumpt
14 | which goimports || GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports
15 | which godot || GO111MODULE=off go get -u github.com/tetafro/godot/cmd/godot
16 |
17 | format: check-formatter
18 | find $(ROOT) -type f -name "*.go" -not -path "$(ROOT)/vendor/*" | xargs -n 1 -I R goimports -w R
19 | find $(ROOT) -type f -name "*.go" -not -path "$(ROOT)/vendor/*" | xargs -n 1 -I R gofmt -s -w R
20 | find $(ROOT) -type f -name "*.go" -not -path "$(ROOT)/vendor/*" | xargs -n 1 -I R gofumpt -s -w R
21 | find $(ROOT) -type f -name "*.go" -not -path "$(ROOT)/vendor/*" | xargs -n 1 -I R godot -w R
22 |
23 | check-linter:
24 | which golangci-lint || GO111MODULE=off curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.30.0
25 |
26 | lint: check-linter
27 | golangci-lint run --deadline 10m $(ROOT)/...
28 |
29 | test:
30 | go test -v -race -p 1 ./...
31 |
32 | ci-test:
33 | go test -v -race -p 1 -coverprofile=coverage.txt -covermode=atomic ./...
34 | go tool cover -func coverage.txt
35 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35 h1:enTowfyfjtomBQhxX9mhUD+0tZhpe4rIzStO4aNlou8=
5 | github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
6 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
10 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
12 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
13 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
14 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
15 |
--------------------------------------------------------------------------------
/lookups.go:
--------------------------------------------------------------------------------
1 | package lookups
2 |
3 | import (
4 | "github.com/golang/geo/s2"
5 | )
6 |
7 | const (
8 | // Default level of s2 cells.
9 | DefaultS2CellLevel = 15
10 | )
11 |
12 | //nolint:gochecknoglobals
13 | var (
14 | // Default value for geo indexer.
15 | DefaultGeoIndexer = NewS2Index(DefaultS2CellLevel)
16 | )
17 |
18 | type (
19 | // Lookuper is an interface for lookup services.
20 | Lookuper interface {
21 | Lookup(coordinates []Coordinate) []CoordinateProps
22 | }
23 |
24 | // lookups is an engine of lookups.
25 | lookups struct {
26 | geoIndex GeoIndex
27 | geoPolygons map[string][]PolyProps
28 | }
29 | )
30 |
31 | // New returns a new lookups engine instance.
32 | func New(polyProps []PolyProps, geoIndex GeoIndex) Lookuper {
33 | geoPolygons := make(map[string][]PolyProps)
34 |
35 | for _, polyProp := range polyProps {
36 | ids := geoIndex.Cover(polyProp.Polygon)
37 |
38 | for _, id := range ids {
39 | geoPolygons[id] = append(geoPolygons[id], polyProp)
40 | }
41 | }
42 |
43 | return &lookups{
44 | geoIndex: geoIndex,
45 | geoPolygons: geoPolygons,
46 | }
47 | }
48 |
49 | // Lookup returns list of properties of given coordinates.
50 | func (l lookups) Lookup(coordinates []Coordinate) []CoordinateProps {
51 | result := make([]CoordinateProps, 0, len(coordinates))
52 |
53 | for _, coordinate := range coordinates {
54 | cell := l.geoIndex.Find(coordinate)
55 | candidates := l.geoPolygons[cell]
56 | props := make([]Props, 0, len(candidates))
57 |
58 | for _, candidate := range candidates {
59 | ll := s2.LatLngFromDegrees(coordinate.Latitude, coordinate.Longitude)
60 | point := s2.PointFromLatLng(ll)
61 |
62 | if candidate.Polygon.ContainsPoint(point) {
63 | props = append(props, candidate.Props)
64 | }
65 | }
66 |
67 | result = append(result, CoordinateProps{
68 | Props: props,
69 | Coordinate: coordinate,
70 | })
71 | }
72 |
73 | return result
74 | }
75 |
--------------------------------------------------------------------------------
/example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/4lie/lookups"
7 | )
8 |
9 | //nolint:gomnd,funlen
10 | func main() {
11 | polyProps := []lookups.PolyProps{
12 | lookups.PolyPropsFromCoordinates([][]lookups.Coordinate{
13 | {
14 | {
15 | Latitude: 35.839542,
16 | Longitude: 50.991776,
17 | },
18 | {
19 | Latitude: 35.834498,
20 | Longitude: 50.991669,
21 | },
22 | {
23 | Latitude: 35.834289,
24 | Longitude: 51.002011,
25 | },
26 | {
27 | Latitude: 35.83956,
28 | Longitude: 51.001968,
29 | },
30 | },
31 | {
32 | {
33 | Latitude: 35.839194,
34 | Longitude: 50.993954,
35 | },
36 | {
37 | Latitude: 35.837316,
38 | Longitude: 50.992484,
39 | },
40 | {
41 | Latitude: 35.836977,
42 | Longitude: 50.996207,
43 | },
44 | },
45 | {
46 | {
47 | Latitude: 35.838012,
48 | Longitude: 50.999587,
49 | },
50 | {
51 | Latitude: 35.835472,
52 | Longitude: 50.997097,
53 | },
54 | {
55 | Latitude: 35.835141,
56 | Longitude: 51.001368,
57 | },
58 | },
59 | },
60 | map[string]interface{}{
61 | "polygon": "blue",
62 | },
63 | ),
64 | lookups.PolyPropsFromCoordinates([][]lookups.Coordinate{
65 | {
66 | {
67 | Latitude: 35.84163,
68 | Longitude: 50.995038,
69 | },
70 | {
71 | Latitude: 35.837455,
72 | Longitude: 51.001432,
73 | },
74 | {
75 | Latitude: 35.841925,
76 | Longitude: 51.001239,
77 | },
78 | },
79 | },
80 | map[string]interface{}{
81 | "polygon": "green",
82 | },
83 | ),
84 | }
85 |
86 | queries := []lookups.Coordinate{
87 | {
88 | Latitude: 35.840743,
89 | Longitude: 50.996776,
90 | },
91 | {
92 | Latitude: 35.837681,
93 | Longitude: 50.994222,
94 | },
95 | {
96 | Latitude: 35.835976,
97 | Longitude: 50.999308,
98 | },
99 | {
100 | Latitude: 35.838429,
101 | Longitude: 51.000767,
102 | },
103 | {
104 | Latitude: 35.835524,
105 | Longitude: 50.993471,
106 | },
107 | }
108 |
109 | lookuper := lookups.New(polyProps, lookups.DefaultGeoIndexer)
110 |
111 | coordinatesProps := lookuper.Lookup(queries)
112 |
113 | for _, coordinateProps := range coordinatesProps {
114 | fmt.Println(coordinateProps.Props)
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/go,intellij
2 | # Edit at https://www.gitignore.io/?templates=go,intellij
3 |
4 | ### Go ###
5 | # Binaries for programs and plugins
6 | *.exe
7 | *.exe~
8 | *.dll
9 | *.so
10 | *.dylib
11 |
12 | # Test binary, built with `go test -c`
13 | *.test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | *.out
17 |
18 | # Dependency directories (remove the comment below to include it)
19 | # vendor/
20 |
21 | ### Go Patch ###
22 | /vendor/
23 | /Godeps/
24 |
25 | ### Intellij ###
26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
27 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
28 |
29 | # User-specific stuff
30 | .idea/**/workspace.xml
31 | .idea/**/tasks.xml
32 | .idea/**/usage.statistics.xml
33 | .idea/**/dictionaries
34 | .idea/**/shelf
35 |
36 | # Generated files
37 | .idea/**/contentModel.xml
38 |
39 | # Sensitive or high-churn files
40 | .idea/**/dataSources/
41 | .idea/**/dataSources.ids
42 | .idea/**/dataSources.local.xml
43 | .idea/**/sqlDataSources.xml
44 | .idea/**/dynamic.xml
45 | .idea/**/uiDesigner.xml
46 | .idea/**/dbnavigator.xml
47 |
48 | # Gradle
49 | .idea/**/gradle.xml
50 | .idea/**/libraries
51 |
52 | # Gradle and Maven with auto-import
53 | # When using Gradle or Maven with auto-import, you should exclude module files,
54 | # since they will be recreated, and may cause churn. Uncomment if using
55 | # auto-import.
56 | # .idea/modules.xml
57 | # .idea/*.iml
58 | # .idea/modules
59 | # *.iml
60 | # *.ipr
61 |
62 | # CMake
63 | cmake-build-*/
64 |
65 | # Mongo Explorer plugin
66 | .idea/**/mongoSettings.xml
67 |
68 | # File-based project format
69 | *.iws
70 |
71 | # IntelliJ
72 | out/
73 |
74 | # mpeltonen/sbt-idea plugin
75 | .idea_modules/
76 |
77 | # JIRA plugin
78 | atlassian-ide-plugin.xml
79 |
80 | # Cursive Clojure plugin
81 | .idea/replstate.xml
82 |
83 | # Crashlytics plugin (for Android Studio and IntelliJ)
84 | com_crashlytics_export_strings.xml
85 | crashlytics.properties
86 | crashlytics-build.properties
87 | fabric.properties
88 |
89 | # Editor-based Rest Client
90 | .idea/httpRequests
91 |
92 | # Android studio 3.1+ serialized cache file
93 | .idea/caches/build_file_checksums.ser
94 |
95 | ### Intellij Patch ###
96 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
97 |
98 | # *.iml
99 | # modules.xml
100 | # .idea/misc.xml
101 | # *.ipr
102 |
103 | # Sonarlint plugin
104 | .idea/**/sonarlint/
105 |
106 | # SonarQube Plugin
107 | .idea/**/sonarIssues.xml
108 |
109 | # Markdown Navigator plugin
110 | .idea/**/markdown-navigator.xml
111 | .idea/**/markdown-navigator/
112 |
113 | # End of https://www.gitignore.io/api/go,intellij
114 |
115 | .idea
116 | .DS_Store
117 | coverage.txt
118 |
--------------------------------------------------------------------------------
/lookups_test.go:
--------------------------------------------------------------------------------
1 | package lookups_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/4lie/lookups"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | //nolint:funlen
11 | func TestLookups(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | polyProps []lookups.PolyProps
15 | queries []lookups.Coordinate
16 | expectedResult [][]lookups.Props
17 | }{
18 | {
19 | name: "simple polygon",
20 | polyProps: []lookups.PolyProps{
21 | lookups.PolyPropsFromCoordinates([][]lookups.Coordinate{
22 | {
23 | {
24 | Latitude: 35.837037430733666,
25 | Longitude: 50.993492603302,
26 | },
27 | {
28 | Latitude: 35.837037430733666,
29 | Longitude: 50.994919538497925,
30 | },
31 | {
32 | Latitude: 35.837837616204105,
33 | Longitude: 50.994919538497925,
34 | },
35 | {
36 | Latitude: 35.837837616204105,
37 | Longitude: 50.993492603302,
38 | },
39 | },
40 | },
41 | map[string]interface{}{
42 | "polygon": "blue",
43 | },
44 | ),
45 | },
46 | queries: []lookups.Coordinate{
47 | {
48 | Latitude: 35.837411,
49 | Longitude: 50.994426,
50 | },
51 | {
52 | Latitude: 35.836411,
53 | Longitude: 50.996733,
54 | },
55 | },
56 | expectedResult: [][]lookups.Props{
57 | {
58 | {"polygon": "blue"},
59 | },
60 | {},
61 | },
62 | },
63 | {
64 | name: "polygon with holes",
65 | polyProps: []lookups.PolyProps{
66 | lookups.PolyPropsFromCoordinates([][]lookups.Coordinate{
67 | {
68 | {
69 | Latitude: 35.839542,
70 | Longitude: 50.991776,
71 | },
72 | {
73 | Latitude: 35.834498,
74 | Longitude: 50.991669,
75 | },
76 | {
77 | Latitude: 35.834289,
78 | Longitude: 51.002011,
79 | },
80 | {
81 | Latitude: 35.83956,
82 | Longitude: 51.001968,
83 | },
84 | },
85 | {
86 | {
87 | Latitude: 35.839194,
88 | Longitude: 50.993954,
89 | },
90 | {
91 | Latitude: 35.837316,
92 | Longitude: 50.992484,
93 | },
94 | {
95 | Latitude: 35.836977,
96 | Longitude: 50.996207,
97 | },
98 | },
99 | {
100 | {
101 | Latitude: 35.838012,
102 | Longitude: 50.999587,
103 | },
104 | {
105 | Latitude: 35.835472,
106 | Longitude: 50.997097,
107 | },
108 | {
109 | Latitude: 35.835141,
110 | Longitude: 51.001368,
111 | },
112 | },
113 | },
114 | map[string]interface{}{
115 | "polygon": "blue",
116 | },
117 | ),
118 | },
119 | queries: []lookups.Coordinate{
120 | {
121 | Latitude: 35.840743,
122 | Longitude: 50.996776,
123 | },
124 | {
125 | Latitude: 35.837681,
126 | Longitude: 50.994222,
127 | },
128 | {
129 | Latitude: 35.835976,
130 | Longitude: 50.999308,
131 | },
132 | {
133 | Latitude: 35.838429,
134 | Longitude: 51.000767,
135 | },
136 | },
137 | expectedResult: [][]lookups.Props{
138 | {},
139 | {},
140 | {},
141 | {
142 | {"polygon": "blue"},
143 | },
144 | },
145 | },
146 | {
147 | name: "polygon with holes and a simple polygon",
148 | polyProps: []lookups.PolyProps{
149 | lookups.PolyPropsFromCoordinates([][]lookups.Coordinate{
150 | {
151 | {
152 | Latitude: 35.839542,
153 | Longitude: 50.991776,
154 | },
155 | {
156 | Latitude: 35.834498,
157 | Longitude: 50.991669,
158 | },
159 | {
160 | Latitude: 35.834289,
161 | Longitude: 51.002011,
162 | },
163 | {
164 | Latitude: 35.83956,
165 | Longitude: 51.001968,
166 | },
167 | },
168 | {
169 | {
170 | Latitude: 35.839194,
171 | Longitude: 50.993954,
172 | },
173 | {
174 | Latitude: 35.837316,
175 | Longitude: 50.992484,
176 | },
177 | {
178 | Latitude: 35.836977,
179 | Longitude: 50.996207,
180 | },
181 | },
182 | {
183 | {
184 | Latitude: 35.838012,
185 | Longitude: 50.999587,
186 | },
187 | {
188 | Latitude: 35.835472,
189 | Longitude: 50.997097,
190 | },
191 | {
192 | Latitude: 35.835141,
193 | Longitude: 51.001368,
194 | },
195 | },
196 | },
197 |
198 | map[string]interface{}{
199 | "polygon": "blue",
200 | },
201 | ),
202 | lookups.PolyPropsFromCoordinates([][]lookups.Coordinate{
203 | {
204 | {
205 | Latitude: 35.84163,
206 | Longitude: 50.995038,
207 | },
208 | {
209 | Latitude: 35.837455,
210 | Longitude: 51.001432,
211 | },
212 | {
213 | Latitude: 35.841925,
214 | Longitude: 51.001239,
215 | },
216 | },
217 | },
218 |
219 | map[string]interface{}{
220 | "polygon": "green",
221 | },
222 | ),
223 | },
224 | queries: []lookups.Coordinate{
225 | {
226 | Latitude: 35.840743,
227 | Longitude: 50.996776,
228 | },
229 | {
230 | Latitude: 35.837681,
231 | Longitude: 50.994222,
232 | },
233 | {
234 | Latitude: 35.835976,
235 | Longitude: 50.999308,
236 | },
237 | {
238 | Latitude: 35.838429,
239 | Longitude: 51.000767,
240 | },
241 | {
242 | Latitude: 35.835524,
243 | Longitude: 50.993471,
244 | },
245 | },
246 | expectedResult: [][]lookups.Props{
247 | {
248 | {"polygon": "green"},
249 | },
250 | {},
251 | {},
252 | {
253 | {"polygon": "blue"},
254 | {"polygon": "green"},
255 | },
256 | {
257 | {"polygon": "blue"},
258 | },
259 | },
260 | },
261 | }
262 | for i := range tests {
263 | test := tests[i]
264 |
265 | t.Run(test.name, func(t *testing.T) {
266 | a := assert.New(t)
267 |
268 | a.Equal(len(test.expectedResult), len(test.queries))
269 |
270 | l := lookups.New(test.polyProps, lookups.DefaultGeoIndexer)
271 |
272 | result := l.Lookup(test.queries)
273 |
274 | a.Equal(len(test.queries), len(result))
275 |
276 | for i := range result {
277 | a.Equal(test.expectedResult[i], result[i].Props)
278 | }
279 | })
280 | }
281 | }
282 |
--------------------------------------------------------------------------------