├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── cmd
└── serve
│ └── main.go
├── go.mod
├── go.sum
├── htdocs
├── index.html
├── leaflet.filelayer.js
├── togeojson.js
└── wasm_exec.js
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.dll
4 | *.so
5 | *.dylib
6 |
7 | # Test binary, build with `go test -c`
8 | *.test
9 |
10 | # Output of the go coverage tool, specifically when used with LiteIDE
11 | *.out
12 |
13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
14 | .glide/
15 |
16 | \.idea/
17 |
18 | \.wercker/
19 |
20 | cmd/serve/serve
21 |
22 | htdocs/ws2\.wasm
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Fabrice Aneche
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.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all serve
2 |
3 | all: cmd/serve/serve ws2.wasm
4 |
5 | cmd/serve/serve: cmd/serve/main.go
6 | go build -o cmd/serve/serve cmd/serve/main.go
7 |
8 | ws2.wasm: main.go
9 | GOARCH=wasm GOOS=js go build -o htdocs/ws2.wasm ./main.go
10 |
11 | build-server: cmd/serve/serve
12 |
13 | all-serve: cmd/serve/serve ws2.wasm
14 | ./cmd/serve/serve
15 |
16 | serve: build-server
17 | ./cmd/serve/serve
18 |
19 | clean:
20 | rm -f cmd/serve/serve htdocs/ws2.wasm
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WS2
2 | ---
3 |
4 | WS2 is simple debug tool to display [S2 covering](https://s2geometry.io/) on a map.
5 |
6 | It does not rely on any backend servers since it's using Wasm to run go code in the browser.
7 |
8 | You can run it from your computer:
9 |
10 | ```
11 | make serve
12 | ```
13 |
14 | Or look at the [demo](https://s2.inair.space)
15 |
16 |
17 | ## License
18 | License is MIT, it includes som js components: [Leaflet.FileLayer](https://github.com/makinacorpus/Leaflet.FileLayer) and [togeojson](https://github.com/mapbox/togeojson)
--------------------------------------------------------------------------------
/cmd/serve/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "log"
6 | "net/http"
7 | "os/exec"
8 | "runtime"
9 | "strings"
10 | "time"
11 | )
12 |
13 | var (
14 | listen = flag.String("listen", ":8080", "listen address")
15 | dir = flag.String("dir", "htdocs", "directory to serve")
16 | openBrowser = flag.Bool("openBrowser", false, "open a browser while serving")
17 | )
18 |
19 | func main() {
20 | flag.Parse()
21 | log.Printf("listening on %s", *listen)
22 |
23 | cmd := ""
24 | switch runtime.GOOS {
25 | case "linux":
26 | cmd = "xdg-open"
27 | case "darwin":
28 | cmd = "open"
29 | case "windows":
30 | cmd = "start"
31 | }
32 | if *openBrowser && cmd != "" {
33 | go func() {
34 | time.Sleep(500 * time.Millisecond)
35 | exec.Command(cmd, "http://127.0.0.1"+*listen).Run()
36 | }()
37 | }
38 | log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
39 | if strings.HasSuffix(req.URL.Path, ".wasm") {
40 | resp.Header().Set("content-type", "application/wasm")
41 | }
42 |
43 | http.FileServer(http.Dir(*dir)).ServeHTTP(resp, req)
44 | })))
45 | }
46 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/akhenakh/ws2
2 |
3 | go 1.16
4 |
5 | require (
6 | github.com/akhenakh/oureadb v0.0.0-20201124024752-f4e3eaada7a3
7 | github.com/golang/geo v0.0.0-20230421003525-6adc56603217
8 | github.com/gorilla/mux v1.8.1 // indirect
9 | github.com/twpayne/go-geom v1.4.1
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4 | github.com/DATA-DOG/go-sqlmock v1.3.2/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
5 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
6 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
7 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
8 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
9 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
10 | github.com/akhenakh/oureadb v0.0.0-20201124024752-f4e3eaada7a3 h1:xR+dg9vmNlTASWdLMq9RQcQzBYYXb+seX9KG6/LhnX0=
11 | github.com/akhenakh/oureadb v0.0.0-20201124024752-f4e3eaada7a3/go.mod h1:9p6P4CSvB6aPA3qer/ikWQX87oa8lVR8fFXgl2Ut2V4=
12 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
13 | github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
14 | github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
15 | github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
16 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
17 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
18 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
19 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23 | github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
24 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
25 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
26 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
27 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
28 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
29 | github.com/golang/geo v0.0.0-20200730024412-e86565bf3f35/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
30 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
31 | github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
32 | github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
33 | github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
34 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
35 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
36 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
37 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
38 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
39 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
40 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
41 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
42 | github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
43 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
44 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
45 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
46 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
47 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
48 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
49 | github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
50 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
51 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
52 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
53 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
54 | github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
55 | github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
56 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
57 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
58 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
59 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
60 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
61 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
62 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
63 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
64 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
65 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
66 | github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
67 | github.com/ory/dockertest/v3 v3.6.0/go.mod h1:4ZOpj8qBUmh8fcBSVzkH2bws2s91JdGvHUqan4GHEuQ=
68 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
69 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
70 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
71 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
72 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
73 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
74 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
75 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
76 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
77 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
78 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
79 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
80 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
81 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
82 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
83 | github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw=
84 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
85 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
86 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
87 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
88 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
89 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
90 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
91 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
92 | github.com/twpayne/go-geom v1.3.6/go.mod h1:XTyWHR6+l9TUYONbbK4ImUTYbWDCu2ySSPrZmmiA0Pg=
93 | github.com/twpayne/go-geom v1.4.1 h1:LeivFqaGBRfyg0XJJ9pkudcptwhSSrYN9KZUW6HcgdA=
94 | github.com/twpayne/go-geom v1.4.1/go.mod h1:k/zktXdL+qnA6OgKsdEGUTA17jbQ2ZPTUa3CCySuGpE=
95 | github.com/twpayne/go-kml v1.5.1/go.mod h1:kz8jAiIz6FIdU2Zjce9qGlVtgFYES9vt7BTPBHf5jl4=
96 | github.com/twpayne/go-kml v1.5.2/go.mod h1:kz8jAiIz6FIdU2Zjce9qGlVtgFYES9vt7BTPBHf5jl4=
97 | github.com/twpayne/go-polyline v1.0.0/go.mod h1:ICh24bcLYBX8CknfvNPKqoTbe+eg+MX1NPyJmSBo7pU=
98 | github.com/twpayne/go-waypoint v0.0.0-20200706203930-b263a7f6e4e8/go.mod h1:qj5pHncxKhu9gxtZEYWypA/z097sxhFlbTyOyt9gcnU=
99 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
100 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
101 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
102 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
103 | golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
104 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
105 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
106 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
107 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
108 | golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
109 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
110 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
111 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
112 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
113 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
114 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
115 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
116 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
117 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
118 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
119 | golang.org/x/sys v0.0.0-20200121082415-34d275377bf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
120 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
121 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
122 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
123 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
124 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
125 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
126 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
127 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
128 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
129 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
130 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
131 | gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
132 |
--------------------------------------------------------------------------------
/htdocs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | S2
7 |
8 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
S2 cover map viewer using Go code compiled into WASM
34 |
35 |
39 |
40 |
41 |
42 |
43 |
44 |
70 |
71 |
72 |
73 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/htdocs/leaflet.filelayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Load files *locally* (GeoJSON, KML, GPX) into the map
3 | * using the HTML5 File API.
4 | *
5 | * Requires Mapbox's togeojson.js to be in global scope
6 | * https://github.com/mapbox/togeojson
7 | */
8 |
9 | (function (factory, window) {
10 | // define an AMD module that relies on 'leaflet'
11 | if (typeof define === 'function' && define.amd && window.toGeoJSON) {
12 | define(['leaflet'], function (L) {
13 | factory(L, window.toGeoJSON);
14 | });
15 | } else if (typeof module === 'object' && module.exports) {
16 | // require('LIBRARY') returns a factory that requires window to
17 | // build a LIBRARY instance, we normalize how we use modules
18 | // that require this pattern but the window provided is a noop
19 | // if it's defined
20 | module.exports = function (root, L, toGeoJSON) {
21 | if (L === undefined) {
22 | if (typeof window !== 'undefined') {
23 | L = require('leaflet');
24 | } else {
25 | L = require('leaflet')(root);
26 | }
27 | }
28 | if (toGeoJSON === undefined) {
29 | if (typeof window !== 'undefined') {
30 | toGeoJSON = require('togeojson');
31 | } else {
32 | toGeoJSON = require('togeojson')(root);
33 | }
34 | }
35 | factory(L, toGeoJSON);
36 | return L;
37 | };
38 | } else if (typeof window !== 'undefined' && window.L && window.toGeoJSON) {
39 | factory(window.L, window.toGeoJSON);
40 | }
41 | }(function fileLoaderFactory(L, toGeoJSON) {
42 | var FileLoader = L.Layer.extend({
43 | options: {
44 | layer: L.geoJson,
45 | layerOptions: {},
46 | fileSizeLimit: 1024
47 | },
48 |
49 | initialize: function (map, options) {
50 | this._map = map;
51 | L.Util.setOptions(this, options);
52 |
53 | this._parsers = {
54 | geojson: this._loadGeoJSON,
55 | json: this._loadGeoJSON,
56 | gpx: this._convertToGeoJSON,
57 | kml: this._convertToGeoJSON
58 | };
59 | },
60 |
61 | load: function (file, ext) {
62 | var parser,
63 | reader;
64 |
65 | // Check file is defined
66 | if (this._isParameterMissing(file, 'file')) {
67 | return false;
68 | }
69 |
70 | // Check file size
71 | if (!this._isFileSizeOk(file.size)) {
72 | return false;
73 | }
74 |
75 | // Get parser for this data type
76 | parser = this._getParser(file.name, ext);
77 | if (!parser) {
78 | return false;
79 | }
80 |
81 | // Read selected file using HTML5 File API
82 | reader = new FileReader();
83 | reader.onload = L.Util.bind(function (e) {
84 | var layer;
85 | try {
86 | this.fire('data:loading', { filename: file.name, format: parser.ext });
87 | layer = parser.processor.call(this, e.target.result, parser.ext);
88 | this.fire('data:loaded', {
89 | layer: layer,
90 | filename: file.name,
91 | format: parser.ext
92 | });
93 | } catch (err) {
94 | this.fire('data:error', { error: err });
95 | }
96 | }, this);
97 | // Testing trick: tests don't pass a real file,
98 | // but an object with file.testing set to true.
99 | // This object cannot be read by reader, just skip it.
100 | if (!file.testing) {
101 | reader.readAsText(file);
102 | }
103 | // We return this to ease testing
104 | return reader;
105 | },
106 |
107 | loadMultiple: function (files, ext) {
108 | var readers = [];
109 | if (files[0]) {
110 | files = Array.prototype.slice.apply(files);
111 | while (files.length > 0) {
112 | readers.push(this.load(files.shift(), ext));
113 | }
114 | }
115 | // return first reader (or false if no file),
116 | // which is also used for subsequent loadings
117 | return readers;
118 | },
119 |
120 | loadData: function (data, name, ext) {
121 | var parser;
122 | var layer;
123 |
124 | // Check required parameters
125 | if ((this._isParameterMissing(data, 'data'))
126 | || (this._isParameterMissing(name, 'name'))) {
127 | return;
128 | }
129 |
130 | // Check file size
131 | if (!this._isFileSizeOk(data.length)) {
132 | return;
133 | }
134 |
135 | // Get parser for this data type
136 | parser = this._getParser(name, ext);
137 | if (!parser) {
138 | return;
139 | }
140 |
141 | // Process data
142 | try {
143 | this.fire('data:loading', { filename: name, format: parser.ext });
144 | layer = parser.processor.call(this, data, parser.ext);
145 | this.fire('data:loaded', {
146 | layer: layer,
147 | filename: name,
148 | format: parser.ext
149 | });
150 | } catch (err) {
151 | this.fire('data:error', { error: err });
152 | }
153 | },
154 |
155 | _isParameterMissing: function (v, vname) {
156 | if (typeof v === 'undefined') {
157 | this.fire('data:error', {
158 | error: new Error('Missing parameter: ' + vname)
159 | });
160 | return true;
161 | }
162 | return false;
163 | },
164 |
165 | _getParser: function (name, ext) {
166 | var parser;
167 | ext = ext || name.split('.').pop();
168 | parser = this._parsers[ext];
169 | if (!parser) {
170 | this.fire('data:error', {
171 | error: new Error('Unsupported file type (' + ext + ')')
172 | });
173 | return undefined;
174 | }
175 | return {
176 | processor: parser,
177 | ext: ext
178 | };
179 | },
180 |
181 | _isFileSizeOk: function (size) {
182 | var fileSize = (size / 1024).toFixed(4);
183 | if (fileSize > this.options.fileSizeLimit) {
184 | this.fire('data:error', {
185 | error: new Error(
186 | 'File size exceeds limit (' +
187 | fileSize + ' > ' +
188 | this.options.fileSizeLimit + 'kb)'
189 | )
190 | });
191 | return false;
192 | }
193 | return true;
194 | },
195 |
196 | _loadGeoJSON: function _loadGeoJSON(content) {
197 | var layer;
198 | if (typeof content === 'string') {
199 | content = JSON.parse(content);
200 | }
201 | layer = this.options.layer(content, this.options.layerOptions);
202 |
203 | if (layer.getLayers().length === 0) {
204 | throw new Error('GeoJSON has no valid layers.');
205 | }
206 |
207 | if (this.options.addToMap) {
208 | layer.addTo(this._map);
209 | }
210 | return layer;
211 | },
212 |
213 | _convertToGeoJSON: function _convertToGeoJSON(content, format) {
214 | var geojson;
215 | // Format is either 'gpx' or 'kml'
216 | if (typeof content === 'string') {
217 | content = (new window.DOMParser()).parseFromString(content, 'text/xml');
218 | }
219 | geojson = toGeoJSON[format](content);
220 | return this._loadGeoJSON(geojson);
221 | }
222 | });
223 |
224 | var FileLayerLoad = L.Control.extend({
225 | statics: {
226 | TITLE: 'Load local file (GPX, KML, GeoJSON)',
227 | LABEL: '⌅'
228 | },
229 | options: {
230 | position: 'topleft',
231 | fitBounds: true,
232 | layerOptions: {},
233 | addToMap: true,
234 | fileSizeLimit: 1024
235 | },
236 |
237 | initialize: function (options) {
238 | L.Util.setOptions(this, options);
239 | this.loader = null;
240 | },
241 |
242 | onAdd: function (map) {
243 | this.loader = L.FileLayer.fileLoader(map, this.options);
244 |
245 | this.loader.on('data:loaded', function (e) {
246 | // Fit bounds after loading
247 | if (this.options.fitBounds) {
248 | window.setTimeout(function () {
249 | map.fitBounds(e.layer.getBounds());
250 | }, 500);
251 | }
252 | }, this);
253 |
254 | // Initialize Drag-and-drop
255 | this._initDragAndDrop(map);
256 |
257 | // Initialize map control
258 | return this._initContainer();
259 | },
260 |
261 | _initDragAndDrop: function (map) {
262 | var callbackName;
263 | var thisLoader = this.loader;
264 | var dropbox = map._container;
265 |
266 | var callbacks = {
267 | dragenter: function () {
268 | map.scrollWheelZoom.disable();
269 | },
270 | dragleave: function () {
271 | map.scrollWheelZoom.enable();
272 | },
273 | dragover: function (e) {
274 | e.stopPropagation();
275 | e.preventDefault();
276 | },
277 | drop: function (e) {
278 | e.stopPropagation();
279 | e.preventDefault();
280 |
281 | thisLoader.loadMultiple(e.dataTransfer.files);
282 | map.scrollWheelZoom.enable();
283 | }
284 | };
285 | for (callbackName in callbacks) {
286 | if (callbacks.hasOwnProperty(callbackName)) {
287 | dropbox.addEventListener(callbackName, callbacks[callbackName], false);
288 | }
289 | }
290 | },
291 |
292 | _initContainer: function () {
293 | var thisLoader = this.loader;
294 |
295 | // Create a button, and bind click on hidden file input
296 | var fileInput;
297 | var zoomName = 'leaflet-control-filelayer leaflet-control-zoom';
298 | var barName = 'leaflet-bar';
299 | var partName = barName + '-part';
300 | var container = L.DomUtil.create('div', zoomName + ' ' + barName);
301 | var link = L.DomUtil.create('a', zoomName + '-in ' + partName, container);
302 | link.innerHTML = L.Control.FileLayerLoad.LABEL;
303 | link.href = '#';
304 | link.title = L.Control.FileLayerLoad.TITLE;
305 |
306 | // Create an invisible file input
307 | fileInput = L.DomUtil.create('input', 'hidden', container);
308 | fileInput.type = 'file';
309 | fileInput.multiple = 'multiple';
310 | if (!this.options.formats) {
311 | fileInput.accept = '.gpx,.kml,.json,.geojson';
312 | } else {
313 | fileInput.accept = this.options.formats.join(',');
314 | }
315 | fileInput.style.display = 'none';
316 | // Load on file change
317 | fileInput.addEventListener('change', function () {
318 | thisLoader.loadMultiple(this.files);
319 | // reset so that the user can upload the same file again if they want to
320 | this.value = '';
321 | }, false);
322 |
323 | L.DomEvent.disableClickPropagation(link);
324 | L.DomEvent.on(link, 'click', function (e) {
325 | fileInput.click();
326 | e.preventDefault();
327 | });
328 | return container;
329 | }
330 | });
331 |
332 | L.FileLayer = {};
333 | L.FileLayer.FileLoader = FileLoader;
334 | L.FileLayer.fileLoader = function (map, options) {
335 | return new L.FileLayer.FileLoader(map, options);
336 | };
337 |
338 | L.Control.FileLayerLoad = FileLayerLoad;
339 | L.Control.fileLayerLoad = function (options) {
340 | return new L.Control.FileLayerLoad(options);
341 | };
342 | }, window));
343 |
--------------------------------------------------------------------------------
/htdocs/togeojson.js:
--------------------------------------------------------------------------------
1 | var toGeoJSON = (function() {
2 | 'use strict';
3 |
4 | var removeSpace = /\s*/g,
5 | trimSpace = /^\s*|\s*$/g,
6 | splitSpace = /\s+/;
7 | // generate a short, numeric hash of a string
8 | function okhash(x) {
9 | if (!x || !x.length) return 0;
10 | for (var i = 0, h = 0; i < x.length; i++) {
11 | h = ((h << 5) - h) + x.charCodeAt(i) | 0;
12 | } return h;
13 | }
14 | // all Y children of X
15 | function get(x, y) { return x.getElementsByTagName(y); }
16 | function attr(x, y) { return x.getAttribute(y); }
17 | function attrf(x, y) { return parseFloat(attr(x, y)); }
18 | // one Y child of X, if any, otherwise null
19 | function get1(x, y) { var n = get(x, y); return n.length ? n[0] : null; }
20 | // https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize
21 | function norm(el) { if (el.normalize) { el.normalize(); } return el; }
22 | // cast array x into numbers
23 | function numarray(x) {
24 | for (var j = 0, o = []; j < x.length; j++) { o[j] = parseFloat(x[j]); }
25 | return o;
26 | }
27 | // get the content of a text node, if any
28 | function nodeVal(x) {
29 | if (x) { norm(x); }
30 | return (x && x.textContent) || '';
31 | }
32 | // get the contents of multiple text nodes, if present
33 | function getMulti(x, ys) {
34 | var o = {}, n, k;
35 | for (k = 0; k < ys.length; k++) {
36 | n = get1(x, ys[k]);
37 | if (n) o[ys[k]] = nodeVal(n);
38 | }
39 | return o;
40 | }
41 | // add properties of Y to X, overwriting if present in both
42 | function extend(x, y) { for (var k in y) x[k] = y[k]; }
43 | // get one coordinate from a coordinate array, if any
44 | function coord1(v) { return numarray(v.replace(removeSpace, '').split(',')); }
45 | // get all coordinates from a coordinate array as [[],[]]
46 | function coord(v) {
47 | var coords = v.replace(trimSpace, '').split(splitSpace),
48 | o = [];
49 | for (var i = 0; i < coords.length; i++) {
50 | o.push(coord1(coords[i]));
51 | }
52 | return o;
53 | }
54 | function coordPair(x) {
55 | var ll = [attrf(x, 'lon'), attrf(x, 'lat')],
56 | ele = get1(x, 'ele'),
57 | // handle namespaced attribute in browser
58 | heartRate = get1(x, 'gpxtpx:hr') || get1(x, 'hr'),
59 | time = get1(x, 'time'),
60 | e;
61 | if (ele) {
62 | e = parseFloat(nodeVal(ele));
63 | if (!isNaN(e)) {
64 | ll.push(e);
65 | }
66 | }
67 | return {
68 | coordinates: ll,
69 | time: time ? nodeVal(time) : null,
70 | heartRate: heartRate ? parseFloat(nodeVal(heartRate)) : null
71 | };
72 | }
73 |
74 | // create a new feature collection parent object
75 | function fc() {
76 | return {
77 | type: 'FeatureCollection',
78 | features: []
79 | };
80 | }
81 |
82 | var serializer;
83 | if (typeof XMLSerializer !== 'undefined') {
84 | /* istanbul ignore next */
85 | serializer = new XMLSerializer();
86 | } else {
87 | var isNodeEnv = (typeof process === 'object' && !process.browser);
88 | var isTitaniumEnv = (typeof Titanium === 'object');
89 | if (typeof exports === 'object' && (isNodeEnv || isTitaniumEnv)) {
90 | serializer = new (require('xmldom').XMLSerializer)();
91 | } else {
92 | throw new Error('Unable to initialize serializer');
93 | }
94 | }
95 | function xml2str(str) {
96 | // IE9 will create a new XMLSerializer but it'll crash immediately.
97 | // This line is ignored because we don't run coverage tests in IE9
98 | /* istanbul ignore next */
99 | if (str.xml !== undefined) return str.xml;
100 | return serializer.serializeToString(str);
101 | }
102 |
103 | var t = {
104 | kml: function(doc) {
105 |
106 | var gj = fc(),
107 | // styleindex keeps track of hashed styles in order to match features
108 | styleIndex = {}, styleByHash = {},
109 | // stylemapindex keeps track of style maps to expose in properties
110 | styleMapIndex = {},
111 | // atomic geospatial types supported by KML - MultiGeometry is
112 | // handled separately
113 | geotypes = ['Polygon', 'LineString', 'Point', 'Track', 'gx:Track'],
114 | // all root placemarks in the file
115 | placemarks = get(doc, 'Placemark'),
116 | styles = get(doc, 'Style'),
117 | styleMaps = get(doc, 'StyleMap');
118 |
119 | for (var k = 0; k < styles.length; k++) {
120 | var hash = okhash(xml2str(styles[k])).toString(16);
121 | styleIndex['#' + attr(styles[k], 'id')] = hash;
122 | styleByHash[hash] = styles[k];
123 | }
124 | for (var l = 0; l < styleMaps.length; l++) {
125 | styleIndex['#' + attr(styleMaps[l], 'id')] = okhash(xml2str(styleMaps[l])).toString(16);
126 | var pairs = get(styleMaps[l], 'Pair');
127 | var pairsMap = {};
128 | for (var m = 0; m < pairs.length; m++) {
129 | pairsMap[nodeVal(get1(pairs[m], 'key'))] = nodeVal(get1(pairs[m], 'styleUrl'));
130 | }
131 | styleMapIndex['#' + attr(styleMaps[l], 'id')] = pairsMap;
132 |
133 | }
134 | for (var j = 0; j < placemarks.length; j++) {
135 | gj.features = gj.features.concat(getPlacemark(placemarks[j]));
136 | }
137 | function kmlColor(v) {
138 | var color, opacity;
139 | v = v || '';
140 | if (v.substr(0, 1) === '#') { v = v.substr(1); }
141 | if (v.length === 6 || v.length === 3) { color = v; }
142 | if (v.length === 8) {
143 | opacity = parseInt(v.substr(0, 2), 16) / 255;
144 | color = '#' + v.substr(6, 2) +
145 | v.substr(4, 2) +
146 | v.substr(2, 2);
147 | }
148 | return [color, isNaN(opacity) ? undefined : opacity];
149 | }
150 | function gxCoord(v) { return numarray(v.split(' ')); }
151 | function gxCoords(root) {
152 | var elems = get(root, 'coord', 'gx'), coords = [], times = [];
153 | if (elems.length === 0) elems = get(root, 'gx:coord');
154 | for (var i = 0; i < elems.length; i++) coords.push(gxCoord(nodeVal(elems[i])));
155 | var timeElems = get(root, 'when');
156 | for (var j = 0; j < timeElems.length; j++) times.push(nodeVal(timeElems[j]));
157 | return {
158 | coords: coords,
159 | times: times
160 | };
161 | }
162 | function getGeometry(root) {
163 | var geomNode, geomNodes, i, j, k, geoms = [], coordTimes = [];
164 | if (get1(root, 'MultiGeometry')) { return getGeometry(get1(root, 'MultiGeometry')); }
165 | if (get1(root, 'MultiTrack')) { return getGeometry(get1(root, 'MultiTrack')); }
166 | if (get1(root, 'gx:MultiTrack')) { return getGeometry(get1(root, 'gx:MultiTrack')); }
167 | for (i = 0; i < geotypes.length; i++) {
168 | geomNodes = get(root, geotypes[i]);
169 | if (geomNodes) {
170 | for (j = 0; j < geomNodes.length; j++) {
171 | geomNode = geomNodes[j];
172 | if (geotypes[i] === 'Point') {
173 | geoms.push({
174 | type: 'Point',
175 | coordinates: coord1(nodeVal(get1(geomNode, 'coordinates')))
176 | });
177 | } else if (geotypes[i] === 'LineString') {
178 | geoms.push({
179 | type: 'LineString',
180 | coordinates: coord(nodeVal(get1(geomNode, 'coordinates')))
181 | });
182 | } else if (geotypes[i] === 'Polygon') {
183 | var rings = get(geomNode, 'LinearRing'),
184 | coords = [];
185 | for (k = 0; k < rings.length; k++) {
186 | coords.push(coord(nodeVal(get1(rings[k], 'coordinates'))));
187 | }
188 | geoms.push({
189 | type: 'Polygon',
190 | coordinates: coords
191 | });
192 | } else if (geotypes[i] === 'Track' ||
193 | geotypes[i] === 'gx:Track') {
194 | var track = gxCoords(geomNode);
195 | geoms.push({
196 | type: 'LineString',
197 | coordinates: track.coords
198 | });
199 | if (track.times.length) coordTimes.push(track.times);
200 | }
201 | }
202 | }
203 | }
204 | return {
205 | geoms: geoms,
206 | coordTimes: coordTimes
207 | };
208 | }
209 | function getPlacemark(root) {
210 | var geomsAndTimes = getGeometry(root), i, properties = {},
211 | name = nodeVal(get1(root, 'name')),
212 | address = nodeVal(get1(root, 'address')),
213 | styleUrl = nodeVal(get1(root, 'styleUrl')),
214 | description = nodeVal(get1(root, 'description')),
215 | timeSpan = get1(root, 'TimeSpan'),
216 | timeStamp = get1(root, 'TimeStamp'),
217 | extendedData = get1(root, 'ExtendedData'),
218 | lineStyle = get1(root, 'LineStyle'),
219 | polyStyle = get1(root, 'PolyStyle'),
220 | visibility = get1(root, 'visibility');
221 |
222 | if (!geomsAndTimes.geoms.length) return [];
223 | if (name) properties.name = name;
224 | if (address) properties.address = address;
225 | if (styleUrl) {
226 | if (styleUrl[0] !== '#') {
227 | styleUrl = '#' + styleUrl;
228 | }
229 |
230 | properties.styleUrl = styleUrl;
231 | if (styleIndex[styleUrl]) {
232 | properties.styleHash = styleIndex[styleUrl];
233 | }
234 | if (styleMapIndex[styleUrl]) {
235 | properties.styleMapHash = styleMapIndex[styleUrl];
236 | properties.styleHash = styleIndex[styleMapIndex[styleUrl].normal];
237 | }
238 | // Try to populate the lineStyle or polyStyle since we got the style hash
239 | var style = styleByHash[properties.styleHash];
240 | if (style) {
241 | if (!lineStyle) lineStyle = get1(style, 'LineStyle');
242 | if (!polyStyle) polyStyle = get1(style, 'PolyStyle');
243 | var iconStyle = get1(style, 'IconStyle');
244 | if (iconStyle) {
245 | var icon = get1(iconStyle, 'Icon');
246 | if (icon) {
247 | var href = nodeVal(get1(icon, 'href'));
248 | if (href) properties.icon = href;
249 | }
250 | }
251 | }
252 | }
253 | if (description) properties.description = description;
254 | if (timeSpan) {
255 | var begin = nodeVal(get1(timeSpan, 'begin'));
256 | var end = nodeVal(get1(timeSpan, 'end'));
257 | properties.timespan = { begin: begin, end: end };
258 | }
259 | if (timeStamp) {
260 | properties.timestamp = nodeVal(get1(timeStamp, 'when'));
261 | }
262 | if (lineStyle) {
263 | var linestyles = kmlColor(nodeVal(get1(lineStyle, 'color'))),
264 | color = linestyles[0],
265 | opacity = linestyles[1],
266 | width = parseFloat(nodeVal(get1(lineStyle, 'width')));
267 | if (color) properties.stroke = color;
268 | if (!isNaN(opacity)) properties['stroke-opacity'] = opacity;
269 | if (!isNaN(width)) properties['stroke-width'] = width;
270 | }
271 | if (polyStyle) {
272 | var polystyles = kmlColor(nodeVal(get1(polyStyle, 'color'))),
273 | pcolor = polystyles[0],
274 | popacity = polystyles[1],
275 | fill = nodeVal(get1(polyStyle, 'fill')),
276 | outline = nodeVal(get1(polyStyle, 'outline'));
277 | if (pcolor) properties.fill = pcolor;
278 | if (!isNaN(popacity)) properties['fill-opacity'] = popacity;
279 | if (fill) properties['fill-opacity'] = fill === '1' ? properties['fill-opacity'] || 1 : 0;
280 | if (outline) properties['stroke-opacity'] = outline === '1' ? properties['stroke-opacity'] || 1 : 0;
281 | }
282 | if (extendedData) {
283 | var datas = get(extendedData, 'Data'),
284 | simpleDatas = get(extendedData, 'SimpleData');
285 |
286 | for (i = 0; i < datas.length; i++) {
287 | properties[datas[i].getAttribute('name')] = nodeVal(get1(datas[i], 'value'));
288 | }
289 | for (i = 0; i < simpleDatas.length; i++) {
290 | properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]);
291 | }
292 | }
293 | if (visibility) {
294 | properties.visibility = nodeVal(visibility);
295 | }
296 | if (geomsAndTimes.coordTimes.length) {
297 | properties.coordTimes = (geomsAndTimes.coordTimes.length === 1) ?
298 | geomsAndTimes.coordTimes[0] : geomsAndTimes.coordTimes;
299 | }
300 | var feature = {
301 | type: 'Feature',
302 | geometry: (geomsAndTimes.geoms.length === 1) ? geomsAndTimes.geoms[0] : {
303 | type: 'GeometryCollection',
304 | geometries: geomsAndTimes.geoms
305 | },
306 | properties: properties
307 | };
308 | if (attr(root, 'id')) feature.id = attr(root, 'id');
309 | return [feature];
310 | }
311 | return gj;
312 | },
313 | gpx: function(doc) {
314 | var i,
315 | tracks = get(doc, 'trk'),
316 | routes = get(doc, 'rte'),
317 | waypoints = get(doc, 'wpt'),
318 | // a feature collection
319 | gj = fc(),
320 | feature;
321 | for (i = 0; i < tracks.length; i++) {
322 | feature = getTrack(tracks[i]);
323 | if (feature) gj.features.push(feature);
324 | }
325 | for (i = 0; i < routes.length; i++) {
326 | feature = getRoute(routes[i]);
327 | if (feature) gj.features.push(feature);
328 | }
329 | for (i = 0; i < waypoints.length; i++) {
330 | gj.features.push(getPoint(waypoints[i]));
331 | }
332 | function initializeArray(arr, size) {
333 | for (var h = 0; h < size; h++) {
334 | arr.push(null);
335 | }
336 | return arr;
337 | }
338 | function getPoints(node, pointname) {
339 | var pts = get(node, pointname),
340 | line = [],
341 | times = [],
342 | heartRates = [],
343 | l = pts.length;
344 | if (l < 2) return {}; // Invalid line in GeoJSON
345 | for (var i = 0; i < l; i++) {
346 | var c = coordPair(pts[i]);
347 | line.push(c.coordinates);
348 | if (c.time) times.push(c.time);
349 | if (c.heartRate || heartRates.length) {
350 | if (!heartRates.length) initializeArray(heartRates, i);
351 | heartRates.push(c.heartRate || null);
352 | }
353 | }
354 | return {
355 | line: line,
356 | times: times,
357 | heartRates: heartRates
358 | };
359 | }
360 | function getTrack(node) {
361 | var segments = get(node, 'trkseg'),
362 | track = [],
363 | times = [],
364 | heartRates = [],
365 | line;
366 | for (var i = 0; i < segments.length; i++) {
367 | line = getPoints(segments[i], 'trkpt');
368 | if (line) {
369 | if (line.line) track.push(line.line);
370 | if (line.times && line.times.length) times.push(line.times);
371 | if (heartRates.length || (line.heartRates && line.heartRates.length)) {
372 | if (!heartRates.length) {
373 | for (var s = 0; s < i; s++) {
374 | heartRates.push(initializeArray([], track[s].length));
375 | }
376 | }
377 | if (line.heartRates && line.heartRates.length) {
378 | heartRates.push(line.heartRates);
379 | } else {
380 | heartRates.push(initializeArray([], line.line.length || 0));
381 | }
382 | }
383 | }
384 | }
385 | if (track.length === 0) return;
386 | var properties = getProperties(node);
387 | extend(properties, getLineStyle(get1(node, 'extensions')));
388 | if (times.length) properties.coordTimes = track.length === 1 ? times[0] : times;
389 | if (heartRates.length) properties.heartRates = track.length === 1 ? heartRates[0] : heartRates;
390 | return {
391 | type: 'Feature',
392 | properties: properties,
393 | geometry: {
394 | type: track.length === 1 ? 'LineString' : 'MultiLineString',
395 | coordinates: track.length === 1 ? track[0] : track
396 | }
397 | };
398 | }
399 | function getRoute(node) {
400 | var line = getPoints(node, 'rtept');
401 | if (!line.line) return;
402 | var prop = getProperties(node);
403 | extend(prop, getLineStyle(get1(node, 'extensions')));
404 | var routeObj = {
405 | type: 'Feature',
406 | properties: prop,
407 | geometry: {
408 | type: 'LineString',
409 | coordinates: line.line
410 | }
411 | };
412 | return routeObj;
413 | }
414 | function getPoint(node) {
415 | var prop = getProperties(node);
416 | extend(prop, getMulti(node, ['sym']));
417 | return {
418 | type: 'Feature',
419 | properties: prop,
420 | geometry: {
421 | type: 'Point',
422 | coordinates: coordPair(node).coordinates
423 | }
424 | };
425 | }
426 | function getLineStyle(extensions) {
427 | var style = {};
428 | if (extensions) {
429 | var lineStyle = get1(extensions, 'line');
430 | if (lineStyle) {
431 | var color = nodeVal(get1(lineStyle, 'color')),
432 | opacity = parseFloat(nodeVal(get1(lineStyle, 'opacity'))),
433 | width = parseFloat(nodeVal(get1(lineStyle, 'width')));
434 | if (color) style.stroke = color;
435 | if (!isNaN(opacity)) style['stroke-opacity'] = opacity;
436 | // GPX width is in mm, convert to px with 96 px per inch
437 | if (!isNaN(width)) style['stroke-width'] = width * 96 / 25.4;
438 | }
439 | }
440 | return style;
441 | }
442 | function getProperties(node) {
443 | var prop = getMulti(node, ['name', 'cmt', 'desc', 'type', 'time', 'keywords']),
444 | links = get(node, 'link');
445 | if (links.length) prop.links = [];
446 | for (var i = 0, link; i < links.length; i++) {
447 | link = { href: attr(links[i], 'href') };
448 | extend(link, getMulti(links[i], ['text', 'type']));
449 | prop.links.push(link);
450 | }
451 | return prop;
452 | }
453 | return gj;
454 | }
455 | };
456 | return t;
457 | })();
458 |
459 | if (typeof module !== 'undefined') module.exports = toGeoJSON;
460 |
--------------------------------------------------------------------------------
/htdocs/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | "use strict";
6 |
7 | (() => {
8 | const enosys = () => {
9 | const err = new Error("not implemented");
10 | err.code = "ENOSYS";
11 | return err;
12 | };
13 |
14 | if (!globalThis.fs) {
15 | let outputBuf = "";
16 | globalThis.fs = {
17 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
18 | writeSync(fd, buf) {
19 | outputBuf += decoder.decode(buf);
20 | const nl = outputBuf.lastIndexOf("\n");
21 | if (nl != -1) {
22 | console.log(outputBuf.substring(0, nl));
23 | outputBuf = outputBuf.substring(nl + 1);
24 | }
25 | return buf.length;
26 | },
27 | write(fd, buf, offset, length, position, callback) {
28 | if (offset !== 0 || length !== buf.length || position !== null) {
29 | callback(enosys());
30 | return;
31 | }
32 | const n = this.writeSync(fd, buf);
33 | callback(null, n);
34 | },
35 | chmod(path, mode, callback) { callback(enosys()); },
36 | chown(path, uid, gid, callback) { callback(enosys()); },
37 | close(fd, callback) { callback(enosys()); },
38 | fchmod(fd, mode, callback) { callback(enosys()); },
39 | fchown(fd, uid, gid, callback) { callback(enosys()); },
40 | fstat(fd, callback) { callback(enosys()); },
41 | fsync(fd, callback) { callback(null); },
42 | ftruncate(fd, length, callback) { callback(enosys()); },
43 | lchown(path, uid, gid, callback) { callback(enosys()); },
44 | link(path, link, callback) { callback(enosys()); },
45 | lstat(path, callback) { callback(enosys()); },
46 | mkdir(path, perm, callback) { callback(enosys()); },
47 | open(path, flags, mode, callback) { callback(enosys()); },
48 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
49 | readdir(path, callback) { callback(enosys()); },
50 | readlink(path, callback) { callback(enosys()); },
51 | rename(from, to, callback) { callback(enosys()); },
52 | rmdir(path, callback) { callback(enosys()); },
53 | stat(path, callback) { callback(enosys()); },
54 | symlink(path, link, callback) { callback(enosys()); },
55 | truncate(path, length, callback) { callback(enosys()); },
56 | unlink(path, callback) { callback(enosys()); },
57 | utimes(path, atime, mtime, callback) { callback(enosys()); },
58 | };
59 | }
60 |
61 | if (!globalThis.process) {
62 | globalThis.process = {
63 | getuid() { return -1; },
64 | getgid() { return -1; },
65 | geteuid() { return -1; },
66 | getegid() { return -1; },
67 | getgroups() { throw enosys(); },
68 | pid: -1,
69 | ppid: -1,
70 | umask() { throw enosys(); },
71 | cwd() { throw enosys(); },
72 | chdir() { throw enosys(); },
73 | }
74 | }
75 |
76 | if (!globalThis.path) {
77 | globalThis.path = {
78 | resolve(...pathSegments) {
79 | return pathSegments.join("/");
80 | }
81 | }
82 | }
83 |
84 | if (!globalThis.crypto) {
85 | throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
86 | }
87 |
88 | if (!globalThis.performance) {
89 | throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
90 | }
91 |
92 | if (!globalThis.TextEncoder) {
93 | throw new Error("globalThis.TextEncoder is not available, polyfill required");
94 | }
95 |
96 | if (!globalThis.TextDecoder) {
97 | throw new Error("globalThis.TextDecoder is not available, polyfill required");
98 | }
99 |
100 | const encoder = new TextEncoder("utf-8");
101 | const decoder = new TextDecoder("utf-8");
102 |
103 | globalThis.Go = class {
104 | constructor() {
105 | this.argv = ["js"];
106 | this.env = {};
107 | this.exit = (code) => {
108 | if (code !== 0) {
109 | console.warn("exit code:", code);
110 | }
111 | };
112 | this._exitPromise = new Promise((resolve) => {
113 | this._resolveExitPromise = resolve;
114 | });
115 | this._pendingEvent = null;
116 | this._scheduledTimeouts = new Map();
117 | this._nextCallbackTimeoutID = 1;
118 |
119 | const setInt64 = (addr, v) => {
120 | this.mem.setUint32(addr + 0, v, true);
121 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
122 | }
123 |
124 | const setInt32 = (addr, v) => {
125 | this.mem.setUint32(addr + 0, v, true);
126 | }
127 |
128 | const getInt64 = (addr) => {
129 | const low = this.mem.getUint32(addr + 0, true);
130 | const high = this.mem.getInt32(addr + 4, true);
131 | return low + high * 4294967296;
132 | }
133 |
134 | const loadValue = (addr) => {
135 | const f = this.mem.getFloat64(addr, true);
136 | if (f === 0) {
137 | return undefined;
138 | }
139 | if (!isNaN(f)) {
140 | return f;
141 | }
142 |
143 | const id = this.mem.getUint32(addr, true);
144 | return this._values[id];
145 | }
146 |
147 | const storeValue = (addr, v) => {
148 | const nanHead = 0x7FF80000;
149 |
150 | if (typeof v === "number" && v !== 0) {
151 | if (isNaN(v)) {
152 | this.mem.setUint32(addr + 4, nanHead, true);
153 | this.mem.setUint32(addr, 0, true);
154 | return;
155 | }
156 | this.mem.setFloat64(addr, v, true);
157 | return;
158 | }
159 |
160 | if (v === undefined) {
161 | this.mem.setFloat64(addr, 0, true);
162 | return;
163 | }
164 |
165 | let id = this._ids.get(v);
166 | if (id === undefined) {
167 | id = this._idPool.pop();
168 | if (id === undefined) {
169 | id = this._values.length;
170 | }
171 | this._values[id] = v;
172 | this._goRefCounts[id] = 0;
173 | this._ids.set(v, id);
174 | }
175 | this._goRefCounts[id]++;
176 | let typeFlag = 0;
177 | switch (typeof v) {
178 | case "object":
179 | if (v !== null) {
180 | typeFlag = 1;
181 | }
182 | break;
183 | case "string":
184 | typeFlag = 2;
185 | break;
186 | case "symbol":
187 | typeFlag = 3;
188 | break;
189 | case "function":
190 | typeFlag = 4;
191 | break;
192 | }
193 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
194 | this.mem.setUint32(addr, id, true);
195 | }
196 |
197 | const loadSlice = (addr) => {
198 | const array = getInt64(addr + 0);
199 | const len = getInt64(addr + 8);
200 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
201 | }
202 |
203 | const loadSliceOfValues = (addr) => {
204 | const array = getInt64(addr + 0);
205 | const len = getInt64(addr + 8);
206 | const a = new Array(len);
207 | for (let i = 0; i < len; i++) {
208 | a[i] = loadValue(array + i * 8);
209 | }
210 | return a;
211 | }
212 |
213 | const loadString = (addr) => {
214 | const saddr = getInt64(addr + 0);
215 | const len = getInt64(addr + 8);
216 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
217 | }
218 |
219 | const testCallExport = (a, b) => {
220 | this._inst.exports.testExport0();
221 | return this._inst.exports.testExport(a, b);
222 | }
223 |
224 | const timeOrigin = Date.now() - performance.now();
225 | this.importObject = {
226 | _gotest: {
227 | add: (a, b) => a + b,
228 | callExport: testCallExport,
229 | },
230 | gojs: {
231 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
232 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
233 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
234 | // This changes the SP, thus we have to update the SP used by the imported function.
235 |
236 | // func wasmExit(code int32)
237 | "runtime.wasmExit": (sp) => {
238 | sp >>>= 0;
239 | const code = this.mem.getInt32(sp + 8, true);
240 | this.exited = true;
241 | delete this._inst;
242 | delete this._values;
243 | delete this._goRefCounts;
244 | delete this._ids;
245 | delete this._idPool;
246 | this.exit(code);
247 | },
248 |
249 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
250 | "runtime.wasmWrite": (sp) => {
251 | sp >>>= 0;
252 | const fd = getInt64(sp + 8);
253 | const p = getInt64(sp + 16);
254 | const n = this.mem.getInt32(sp + 24, true);
255 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
256 | },
257 |
258 | // func resetMemoryDataView()
259 | "runtime.resetMemoryDataView": (sp) => {
260 | sp >>>= 0;
261 | this.mem = new DataView(this._inst.exports.mem.buffer);
262 | },
263 |
264 | // func nanotime1() int64
265 | "runtime.nanotime1": (sp) => {
266 | sp >>>= 0;
267 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
268 | },
269 |
270 | // func walltime() (sec int64, nsec int32)
271 | "runtime.walltime": (sp) => {
272 | sp >>>= 0;
273 | const msec = (new Date).getTime();
274 | setInt64(sp + 8, msec / 1000);
275 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
276 | },
277 |
278 | // func scheduleTimeoutEvent(delay int64) int32
279 | "runtime.scheduleTimeoutEvent": (sp) => {
280 | sp >>>= 0;
281 | const id = this._nextCallbackTimeoutID;
282 | this._nextCallbackTimeoutID++;
283 | this._scheduledTimeouts.set(id, setTimeout(
284 | () => {
285 | this._resume();
286 | while (this._scheduledTimeouts.has(id)) {
287 | // for some reason Go failed to register the timeout event, log and try again
288 | // (temporary workaround for https://github.com/golang/go/issues/28975)
289 | console.warn("scheduleTimeoutEvent: missed timeout event");
290 | this._resume();
291 | }
292 | },
293 | getInt64(sp + 8),
294 | ));
295 | this.mem.setInt32(sp + 16, id, true);
296 | },
297 |
298 | // func clearTimeoutEvent(id int32)
299 | "runtime.clearTimeoutEvent": (sp) => {
300 | sp >>>= 0;
301 | const id = this.mem.getInt32(sp + 8, true);
302 | clearTimeout(this._scheduledTimeouts.get(id));
303 | this._scheduledTimeouts.delete(id);
304 | },
305 |
306 | // func getRandomData(r []byte)
307 | "runtime.getRandomData": (sp) => {
308 | sp >>>= 0;
309 | crypto.getRandomValues(loadSlice(sp + 8));
310 | },
311 |
312 | // func finalizeRef(v ref)
313 | "syscall/js.finalizeRef": (sp) => {
314 | sp >>>= 0;
315 | const id = this.mem.getUint32(sp + 8, true);
316 | this._goRefCounts[id]--;
317 | if (this._goRefCounts[id] === 0) {
318 | const v = this._values[id];
319 | this._values[id] = null;
320 | this._ids.delete(v);
321 | this._idPool.push(id);
322 | }
323 | },
324 |
325 | // func stringVal(value string) ref
326 | "syscall/js.stringVal": (sp) => {
327 | sp >>>= 0;
328 | storeValue(sp + 24, loadString(sp + 8));
329 | },
330 |
331 | // func valueGet(v ref, p string) ref
332 | "syscall/js.valueGet": (sp) => {
333 | sp >>>= 0;
334 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
335 | sp = this._inst.exports.getsp() >>> 0; // see comment above
336 | storeValue(sp + 32, result);
337 | },
338 |
339 | // func valueSet(v ref, p string, x ref)
340 | "syscall/js.valueSet": (sp) => {
341 | sp >>>= 0;
342 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
343 | },
344 |
345 | // func valueDelete(v ref, p string)
346 | "syscall/js.valueDelete": (sp) => {
347 | sp >>>= 0;
348 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
349 | },
350 |
351 | // func valueIndex(v ref, i int) ref
352 | "syscall/js.valueIndex": (sp) => {
353 | sp >>>= 0;
354 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
355 | },
356 |
357 | // valueSetIndex(v ref, i int, x ref)
358 | "syscall/js.valueSetIndex": (sp) => {
359 | sp >>>= 0;
360 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
361 | },
362 |
363 | // func valueCall(v ref, m string, args []ref) (ref, bool)
364 | "syscall/js.valueCall": (sp) => {
365 | sp >>>= 0;
366 | try {
367 | const v = loadValue(sp + 8);
368 | const m = Reflect.get(v, loadString(sp + 16));
369 | const args = loadSliceOfValues(sp + 32);
370 | const result = Reflect.apply(m, v, args);
371 | sp = this._inst.exports.getsp() >>> 0; // see comment above
372 | storeValue(sp + 56, result);
373 | this.mem.setUint8(sp + 64, 1);
374 | } catch (err) {
375 | sp = this._inst.exports.getsp() >>> 0; // see comment above
376 | storeValue(sp + 56, err);
377 | this.mem.setUint8(sp + 64, 0);
378 | }
379 | },
380 |
381 | // func valueInvoke(v ref, args []ref) (ref, bool)
382 | "syscall/js.valueInvoke": (sp) => {
383 | sp >>>= 0;
384 | try {
385 | const v = loadValue(sp + 8);
386 | const args = loadSliceOfValues(sp + 16);
387 | const result = Reflect.apply(v, undefined, args);
388 | sp = this._inst.exports.getsp() >>> 0; // see comment above
389 | storeValue(sp + 40, result);
390 | this.mem.setUint8(sp + 48, 1);
391 | } catch (err) {
392 | sp = this._inst.exports.getsp() >>> 0; // see comment above
393 | storeValue(sp + 40, err);
394 | this.mem.setUint8(sp + 48, 0);
395 | }
396 | },
397 |
398 | // func valueNew(v ref, args []ref) (ref, bool)
399 | "syscall/js.valueNew": (sp) => {
400 | sp >>>= 0;
401 | try {
402 | const v = loadValue(sp + 8);
403 | const args = loadSliceOfValues(sp + 16);
404 | const result = Reflect.construct(v, args);
405 | sp = this._inst.exports.getsp() >>> 0; // see comment above
406 | storeValue(sp + 40, result);
407 | this.mem.setUint8(sp + 48, 1);
408 | } catch (err) {
409 | sp = this._inst.exports.getsp() >>> 0; // see comment above
410 | storeValue(sp + 40, err);
411 | this.mem.setUint8(sp + 48, 0);
412 | }
413 | },
414 |
415 | // func valueLength(v ref) int
416 | "syscall/js.valueLength": (sp) => {
417 | sp >>>= 0;
418 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
419 | },
420 |
421 | // valuePrepareString(v ref) (ref, int)
422 | "syscall/js.valuePrepareString": (sp) => {
423 | sp >>>= 0;
424 | const str = encoder.encode(String(loadValue(sp + 8)));
425 | storeValue(sp + 16, str);
426 | setInt64(sp + 24, str.length);
427 | },
428 |
429 | // valueLoadString(v ref, b []byte)
430 | "syscall/js.valueLoadString": (sp) => {
431 | sp >>>= 0;
432 | const str = loadValue(sp + 8);
433 | loadSlice(sp + 16).set(str);
434 | },
435 |
436 | // func valueInstanceOf(v ref, t ref) bool
437 | "syscall/js.valueInstanceOf": (sp) => {
438 | sp >>>= 0;
439 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
440 | },
441 |
442 | // func copyBytesToGo(dst []byte, src ref) (int, bool)
443 | "syscall/js.copyBytesToGo": (sp) => {
444 | sp >>>= 0;
445 | const dst = loadSlice(sp + 8);
446 | const src = loadValue(sp + 32);
447 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
448 | this.mem.setUint8(sp + 48, 0);
449 | return;
450 | }
451 | const toCopy = src.subarray(0, dst.length);
452 | dst.set(toCopy);
453 | setInt64(sp + 40, toCopy.length);
454 | this.mem.setUint8(sp + 48, 1);
455 | },
456 |
457 | // func copyBytesToJS(dst ref, src []byte) (int, bool)
458 | "syscall/js.copyBytesToJS": (sp) => {
459 | sp >>>= 0;
460 | const dst = loadValue(sp + 8);
461 | const src = loadSlice(sp + 16);
462 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
463 | this.mem.setUint8(sp + 48, 0);
464 | return;
465 | }
466 | const toCopy = src.subarray(0, dst.length);
467 | dst.set(toCopy);
468 | setInt64(sp + 40, toCopy.length);
469 | this.mem.setUint8(sp + 48, 1);
470 | },
471 |
472 | "debug": (value) => {
473 | console.log(value);
474 | },
475 | }
476 | };
477 | }
478 |
479 | async run(instance) {
480 | if (!(instance instanceof WebAssembly.Instance)) {
481 | throw new Error("Go.run: WebAssembly.Instance expected");
482 | }
483 | this._inst = instance;
484 | this.mem = new DataView(this._inst.exports.mem.buffer);
485 | this._values = [ // JS values that Go currently has references to, indexed by reference id
486 | NaN,
487 | 0,
488 | null,
489 | true,
490 | false,
491 | globalThis,
492 | this,
493 | ];
494 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
495 | this._ids = new Map([ // mapping from JS values to reference ids
496 | [0, 1],
497 | [null, 2],
498 | [true, 3],
499 | [false, 4],
500 | [globalThis, 5],
501 | [this, 6],
502 | ]);
503 | this._idPool = []; // unused ids that have been garbage collected
504 | this.exited = false; // whether the Go program has exited
505 |
506 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
507 | let offset = 4096;
508 |
509 | const strPtr = (str) => {
510 | const ptr = offset;
511 | const bytes = encoder.encode(str + "\0");
512 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
513 | offset += bytes.length;
514 | if (offset % 8 !== 0) {
515 | offset += 8 - (offset % 8);
516 | }
517 | return ptr;
518 | };
519 |
520 | const argc = this.argv.length;
521 |
522 | const argvPtrs = [];
523 | this.argv.forEach((arg) => {
524 | argvPtrs.push(strPtr(arg));
525 | });
526 | argvPtrs.push(0);
527 |
528 | const keys = Object.keys(this.env).sort();
529 | keys.forEach((key) => {
530 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
531 | });
532 | argvPtrs.push(0);
533 |
534 | const argv = offset;
535 | argvPtrs.forEach((ptr) => {
536 | this.mem.setUint32(offset, ptr, true);
537 | this.mem.setUint32(offset + 4, 0, true);
538 | offset += 8;
539 | });
540 |
541 | // The linker guarantees global data starts from at least wasmMinDataAddr.
542 | // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
543 | const wasmMinDataAddr = 4096 + 8192;
544 | if (offset >= wasmMinDataAddr) {
545 | throw new Error("total length of command line and environment variables exceeds limit");
546 | }
547 |
548 | this._inst.exports.run(argc, argv);
549 | if (this.exited) {
550 | this._resolveExitPromise();
551 | }
552 | await this._exitPromise;
553 | }
554 |
555 | _resume() {
556 | if (this.exited) {
557 | throw new Error("Go program has already exited");
558 | }
559 | this._inst.exports.resume();
560 | if (this.exited) {
561 | this._resolveExitPromise();
562 | }
563 | }
564 |
565 | _makeFuncWrapper(id) {
566 | const go = this;
567 | return function () {
568 | const event = { id: id, this: this, args: arguments };
569 | go._pendingEvent = event;
570 | go._resume();
571 | return event.result;
572 | };
573 | }
574 | }
575 | })();
576 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "math"
7 | "strconv"
8 | "syscall/js"
9 |
10 | "github.com/akhenakh/oureadb/index/geodata"
11 | "github.com/akhenakh/oureadb/s2tools"
12 | "github.com/golang/geo/s2"
13 | "github.com/twpayne/go-geom/encoding/geojson"
14 | )
15 |
16 | const earthCircumferenceMeter = 40075017
17 |
18 | var document js.Value
19 |
20 | func init() {
21 | document = js.Global().Get("document")
22 | }
23 |
24 | func getCoverParams() (minLevel, maxLevel, maxCells int, inside bool) {
25 | minS := document.Call("getElementById", "minRange").Get("value").String()
26 | minLevel, err := strconv.Atoi(minS)
27 | if err != nil {
28 | println(err.Error())
29 | return
30 | }
31 |
32 | maxS := document.Call("getElementById", "maxRange").Get("value").String()
33 | maxLevel, err = strconv.Atoi(maxS)
34 | if err != nil {
35 | println(err.Error())
36 | return
37 | }
38 |
39 | maxCS := document.Call("getElementById", "maxCellsRange").Get("value").String()
40 | maxCells, err = strconv.Atoi(maxCS)
41 | if err != nil {
42 | println(err.Error())
43 | return
44 | }
45 |
46 | coverCS := document.Call("getElementById", "icover").Get("checked").Bool()
47 | if coverCS {
48 | inside = true
49 | }
50 |
51 | return minLevel, maxLevel, maxCells, inside
52 | }
53 |
54 | func geoFeaturesJSONToCells(this js.Value, i []js.Value) interface{} {
55 | var fc geojson.FeatureCollection
56 | b := js.ValueOf(i[0]).String()
57 |
58 | err := json.Unmarshal([]byte(b), &fc)
59 | if err != nil {
60 | println(err.Error())
61 | return nil
62 | }
63 | var res s2.CellUnion
64 | for _, f := range fc.Features {
65 | cu, err := computeFeatureCells(f)
66 | if err != nil {
67 | println("error computing cells", err)
68 | return nil
69 | }
70 | res = append(res, cu...)
71 | }
72 |
73 | jsonb := s2tools.CellUnionToGeoJSON(res)
74 | if len(jsonb) == 0 {
75 | println("empty result")
76 | return nil
77 | }
78 | updateUIWithData(string(jsonb))
79 | return nil
80 | }
81 |
82 | func geoCircleToCells(this js.Value, i []js.Value) interface{} {
83 | lng := js.ValueOf(i[0]).Float()
84 | lat := js.ValueOf(i[1]).Float()
85 | radius := js.ValueOf(i[2]).Float()
86 |
87 | center := s2.PointFromLatLng(s2.LatLngFromDegrees(lat, lng))
88 | cap := s2.CapFromCenterArea(center, s2RadialAreaMeters(radius))
89 |
90 | minLevel, maxLevel, maxCells, inside := getCoverParams()
91 | coverer := &s2.RegionCoverer{MinLevel: minLevel, MaxLevel: maxLevel, MaxCells: maxCells}
92 | var cu s2.CellUnion
93 | if inside {
94 | cu = coverer.InteriorCovering(cap)
95 | } else {
96 | cu = coverer.Covering(cap)
97 | }
98 | jsonb := s2tools.CellUnionToGeoJSON(cu)
99 | updateUIWithData(string(jsonb))
100 | return nil
101 | }
102 |
103 | func geoJSONToCells(this js.Value, i []js.Value) interface{} {
104 | var f geojson.Feature
105 | b := js.ValueOf(i[0]).String()
106 |
107 | err := json.Unmarshal([]byte(b), &f)
108 | if err != nil {
109 | println(err.Error())
110 | return nil
111 | }
112 | cu, err := computeFeatureCells(&f)
113 | if err != nil {
114 | println("error computing cells", err)
115 | return nil
116 | }
117 | jsonb := s2tools.CellUnionToGeoJSON(cu)
118 | if len(jsonb) == 0 {
119 | println("can't generate cells")
120 | return nil
121 | }
122 |
123 | updateUIWithData(string(jsonb))
124 | return nil
125 | }
126 |
127 | func computeFeatureCells(f *geojson.Feature) (s2.CellUnion, error) {
128 | gd := &geodata.GeoData{}
129 | err := geodata.GeoJSONFeatureToGeoData(f, gd)
130 | if err != nil {
131 | return nil, err
132 | }
133 |
134 | minLevel, maxLevel, maxCells, insideCover := getCoverParams()
135 | coverer := &s2.RegionCoverer{MinLevel: minLevel, MaxLevel: maxLevel, MaxCells: maxCells}
136 |
137 | var cu s2.CellUnion
138 | if insideCover {
139 | icu, err := gd.InteriorCover(coverer)
140 | if err != nil {
141 | return nil, fmt.Errorf("error in Interior Cover %w", err)
142 | }
143 | cu = icu
144 | } else {
145 | icu, err := gd.Cover(coverer)
146 | if err != nil {
147 | return nil, fmt.Errorf("error in Exterior Cover %w", err)
148 | }
149 | cu = icu
150 | }
151 |
152 | return cu, nil
153 | }
154 |
155 | func drawCells(this js.Value, i []js.Value) interface{} {
156 | un := make(map[s2.CellID]struct{})
157 | for _, cs := range i {
158 | cs := js.ValueOf(cs).String()
159 | if cs != "" {
160 | c := s2tools.ParseCellID(cs)
161 | if c == nil {
162 | continue
163 | }
164 | un[*c] = struct{}{}
165 | }
166 | }
167 |
168 | cells := make(s2.CellUnion, len(un))
169 | count := 0
170 | for c := range un {
171 | cells[count] = c
172 | count++
173 | }
174 | b := s2tools.CellUnionToGeoJSON(cells)
175 | updateUIWithData(string(b))
176 | return nil
177 | }
178 |
179 | func updateUIWithData(data string) {
180 | js.Global().Set("data", data)
181 | js.Global().Call("updateui")
182 | }
183 |
184 | func registerCallbacks() {
185 | js.Global().Set("drawcells", js.FuncOf(drawCells))
186 | js.Global().Set("circlecell", js.FuncOf(geoCircleToCells))
187 | js.Global().Set("geocell", js.FuncOf(geoJSONToCells))
188 | js.Global().Set("geofeaturescell", js.FuncOf(geoFeaturesJSONToCells))
189 | }
190 |
191 | func s2RadialAreaMeters(radius float64) float64 {
192 | r := (radius / earthCircumferenceMeter) * math.Pi * 2
193 | return math.Pi * r * r
194 | }
195 |
196 | func main() {
197 | c := make(chan struct{}, 0)
198 | println("Wasm ready")
199 | registerCallbacks()
200 | <-c
201 | }
202 |
--------------------------------------------------------------------------------