├── .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 |
36 | Fork 37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 | 47 | 1 48 | 49 | 50 | 51 | 10 52 | 53 | 54 | 55 | 600 56 | 57 | 58 | 59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 | 67 |
68 | 69 |
70 | 71 | 72 |
73 |
74 | 0 75 | 76 |
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 | --------------------------------------------------------------------------------