kopia lustrzana https://github.com/jakecoppinger/safe-cycling-map
Porównaj commity
19 Commity
84a8f2b452
...
bdca4fbecc
Autor | SHA1 | Data |
---|---|---|
Jake Coppinger | bdca4fbecc | |
Jake Coppinger | eb01aac738 | |
Jake Coppinger | 0234c6a747 | |
Jake Coppinger | 0c13b5b909 | |
Jake Coppinger | 68cd11a980 | |
Jake Coppinger | e484e23299 | |
Jake Coppinger | a7110eef7f | |
Jake Coppinger | 7fbd8ac1d3 | |
Jake Coppinger | 66a3ad80f3 | |
Jake Coppinger | 8f4446b462 | |
Jake Coppinger | 865be63b88 | |
Jake Coppinger | 9861006cf1 | |
Jake Coppinger | 26feb770aa | |
Jake Coppinger | 751eae09ab | |
Jake Coppinger | 43166ef98a | |
Jake Coppinger | f5accf4a8f | |
Jake Coppinger | d2efd8ad67 | |
Jake Coppinger | dd7edf9b72 | |
Jake Coppinger | 0b9ffb2ed1 |
21
README.md
21
README.md
|
@ -1,8 +1,26 @@
|
|||
Safe Cycling Map
|
||||
================
|
||||
|
||||
Work in progress! PRs and forks very welcome :)
|
||||
A map showing how safe a street is for cycling, based on (arbitrary) metrics. See the
|
||||
[key](https://github.com/jakecoppinger/safe-cycling-map/blob/main/docs/key.md) for how street safety is calculated.
|
||||
|
||||
This is a work in progress side project. This data is not guaranteed to be accurate.
|
||||
|
||||
When zoomed in close, individual road and bicycle lanes are shown. When zoomed out, streets are
|
||||
coloured by their safety ratings.
|
||||
|
||||
# Disclaimer
|
||||
Warning: This is an arbitrary rating system. Data is open source and not guaranteed to be accurate.
|
||||
|
||||
This map uses OpenStreetMap data. It is not a complete or accurate map of the world and should not
|
||||
be used in such a manner that deficiencies, omissions, inaccuracies or errors could result in death,
|
||||
loss or injury. The maps are an iterative ongoing work-in-progress and everyone is welcome to
|
||||
contribute editing the OpenStreetMap data if you spot inaccuracies. (warning courtesy of [CyclOSM](https://www.cyclosm.org/))
|
||||
|
||||
# Contributing: Found a mislabelled street? You can fix it!
|
||||
|
||||
Head to https://bikemaps.org/blog/post/improving-bicycling-data-on-openstreetmap for instructions
|
||||
on how to fix OpenStreetMap data.
|
||||
|
||||
![Screenshot of map](img/safe-cycling-map-2022-01-05-v2.jpg)
|
||||
|
||||
|
@ -12,6 +30,7 @@ Uses [osm2streets-vector-tileserver](https://github.com/jakecoppinger/osm2street
|
|||
a vector tileserver I wrote to generate Protobuf GeoJSON vector tiles using the JS bindings to
|
||||
osm2streets (which is written in Rust).
|
||||
|
||||
|
||||
# Local development
|
||||
|
||||
See instructions for setting up the backend tileserve at
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
Safe Cycling Map
|
||||
================
|
||||
Zoom in to see individual lanes, zoom out a little to see street safety.
|
||||
|
||||
Warning: This is an arbitrary rating system. Data is open source and not guaranteed to be accurate.
|
||||
|
||||
This map uses OpenStreetMap data. It is not a complete or accurate map of the world and should not
|
||||
be used in such a manner that deficiencies, omissions, inaccuracies or errors could result in death,
|
||||
loss or injury. The maps are an iterative ongoing work-in-progress and everyone is welcome to
|
||||
contribute editing the OpenStreetMap data if you spot inaccuracies. (warning courtesy of [CyclOSM](https://www.cyclosm.org/))
|
||||
|
||||
See the README at https://github.com/jakecoppinger/safe-cycling-map for how to fix the data (or
|
||||
propose improvments to the safety rating system).
|
||||
|
||||
# Key
|
||||
## Safe streets - green
|
||||
The street satisfies any of the below conditions:
|
||||
- The speed is less than or equal to 30kph
|
||||
- It's a [living street](https://wiki.openstreetmap.org/wiki/Tag:highway%3Dliving_street)
|
||||
- Is has a separated cycleway
|
||||
- It has a cycle lane separated from the road
|
||||
- Is has a shared path (bikes + pedestrians allowed)
|
||||
|
||||
## More dangerous streets
|
||||
The street satisfies any of the below conditions:
|
||||
- It has a speed limit less than 40kph and greater than 30kmh
|
||||
- It has an on road, painted (non-separated) bike lane
|
||||
|
||||
## Dangerous streets
|
||||
The street satisfies any of the below conditions:
|
||||
- It has a speed higher than 40kmh
|
||||
- It is a residental street with the default speed limit (assumed to be 50kph)
|
||||
|
||||
## TODO: Banned streets
|
||||
Currently banned streets (eg. motorways/Sydney Harbour Bridge) are currently displayed as red.
|
||||
|
||||
# Technical details
|
||||
|
||||
These safety ratings are calculated in
|
||||
https://github.com/jakecoppinger/safe-cycling-map/blob/main/src/osm-selectors.ts
|
||||
|
||||
PRs are very welcome!
|
|
@ -12,6 +12,8 @@
|
|||
"@mapbox/mapbox-gl-directions": "^4.1.1",
|
||||
"@mapbox/mapbox-gl-geocoder": "^5.0.1",
|
||||
"@svgr/webpack": "4.3.3",
|
||||
"@types/debounce": "^1.2.1",
|
||||
"@types/osmtogeojson": "^2.2.30",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
|
@ -22,6 +24,7 @@
|
|||
"camelcase": "^5.3.1",
|
||||
"case-sensitive-paths-webpack-plugin": "2.3.0",
|
||||
"css-loader": "3.4.2",
|
||||
"debounce": "^1.2.1",
|
||||
"dotenv": "8.2.0",
|
||||
"dotenv-expand": "5.1.0",
|
||||
"eslint": "^6.6.0",
|
||||
|
@ -43,6 +46,7 @@
|
|||
"mapbox-gl": "^2.9.2",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"osmtogeojson": "^3.0.0-beta.5",
|
||||
"pnp-webpack-plugin": "1.6.4",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
|
@ -2396,6 +2400,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"node_modules/@types/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA=="
|
||||
},
|
||||
"node_modules/@types/eslint-visitor-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
|
||||
|
@ -2405,7 +2414,7 @@
|
|||
"version": "7946.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
||||
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/glob": {
|
||||
"version": "7.1.3",
|
||||
|
@ -2514,6 +2523,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw=="
|
||||
},
|
||||
"node_modules/@types/osmtogeojson": {
|
||||
"version": "2.2.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/osmtogeojson/-/osmtogeojson-2.2.30.tgz",
|
||||
"integrity": "sha512-2TE8PjRSqDvlSCNUU3jln9KVKERXwOAVR7Yhxaww0ST9WSqylIAT0itivECUyvuK83/JkIsCnExS4yNobP8SVg=="
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -3001,6 +3015,14 @@
|
|||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.3.tgz",
|
||||
"integrity": "sha512-Lv2vySXypg4nfa51LY1nU8yDAGo/5YwF+EY/rUZgIbfvwVARcd67ttCM8SMsTeJy51YhHYavEq+FS6R0hW9PFQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
|
@ -5092,6 +5114,20 @@
|
|||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"engines": [
|
||||
"node >= 6.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/confusing-browser-globals": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
|
||||
|
@ -5741,6 +5777,11 @@
|
|||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
|
@ -8054,6 +8095,18 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/geojson-numeric": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/geojson-numeric/-/geojson-numeric-0.2.1.tgz",
|
||||
"integrity": "sha512-rvItMp3W7pe16o2EQTnRw54v6WHdiE4bYjUsdr3FZskFb6oPC7gjLe4zginP+Wd1B/HLl2acTukfn16Lmwn7lg==",
|
||||
"dependencies": {
|
||||
"concat-stream": "2.0.0",
|
||||
"optimist": "~0.3.5"
|
||||
},
|
||||
"bin": {
|
||||
"geojson-numeric": "geojson-numeric"
|
||||
}
|
||||
},
|
||||
"node_modules/geojson-vt": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
|
||||
|
@ -10277,6 +10330,31 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonparse": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz",
|
||||
"integrity": "sha512-fw7Q/8gFR8iSekUi9I+HqWIap6mywuoe7hQIg3buTVjuZgALKj4HAmm0X6f+TaL4c9NJbvyFQdaI2ppr5p6dnQ==",
|
||||
"engines": [
|
||||
"node >= 0.2.0"
|
||||
]
|
||||
},
|
||||
"node_modules/JSONStream": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.0.tgz",
|
||||
"integrity": "sha512-PiV28BpoUorz9kKFwRbD7+wg0t/k0ITHKn0DgCU44YZ/GaGAZRPt9q5PzoifC85gE55SEPIdMu0Labfxevj8cw==",
|
||||
"dependencies": {
|
||||
"jsonparse": "0.0.5",
|
||||
"through": "~2.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/JSONStream/node_modules/through": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz",
|
||||
"integrity": "sha512-JIR0m0ybkmTcR8URann+HbwKmodP+OE8UCbsifQDYMLD5J3em1Cdn3MYPpbEd5elGDwmP98T+WbqP/tvzA5Mjg=="
|
||||
},
|
||||
"node_modules/jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
|
@ -11958,6 +12036,14 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/optimist": {
|
||||
"version": "0.3.7",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
|
||||
"integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==",
|
||||
"dependencies": {
|
||||
"wordwrap": "~0.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/optimize-css-assets-webpack-plugin": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz",
|
||||
|
@ -12020,6 +12106,84 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/osm-polygon-features": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/osm-polygon-features/-/osm-polygon-features-0.9.2.tgz",
|
||||
"integrity": "sha512-5zNEFCq+G6X2TDkqbKYLF1+GtWVCCLA8zX+FVhSogsiTRsGquyaGRy5cYNW4BE3ci0MKOLvNTkFNsjsCNtgz0A=="
|
||||
},
|
||||
"node_modules/osmtogeojson": {
|
||||
"version": "3.0.0-beta.5",
|
||||
"resolved": "https://registry.npmjs.org/osmtogeojson/-/osmtogeojson-3.0.0-beta.5.tgz",
|
||||
"integrity": "sha512-izvaUWnunrYvMB4LB0ZN15O1+g90c628yHS4SeSR3daVSBF9vdTHL7iVHfg0wEr1uEYjQ+lMJHCiYFusL5yKVg==",
|
||||
"dependencies": {
|
||||
"@mapbox/geojson-rewind": "0.5.2",
|
||||
"@xmldom/xmldom": "0.8.3",
|
||||
"concat-stream": "2.0.0",
|
||||
"geojson-numeric": "0.2.1",
|
||||
"htmlparser2": "3.5.1",
|
||||
"JSONStream": "0.8.0",
|
||||
"optimist": "~0.3.5",
|
||||
"osm-polygon-features": "^0.9.1",
|
||||
"tiny-osmpbf": "^0.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"osmtogeojson": "osmtogeojson"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.5"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/geojson": "^7946.0"
|
||||
}
|
||||
},
|
||||
"node_modules/osmtogeojson/node_modules/domhandler": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.2.1.tgz",
|
||||
"integrity": "sha512-MFFBQFGkyTuNe3vL9WEw9JdlCwIoBYpOGESLeZAvc/jClYNsOl6P1KzevJbWg76GovdEycfR7/2/Ra7NnqtMKw==",
|
||||
"dependencies": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/osmtogeojson/node_modules/domutils": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.3.0.tgz",
|
||||
"integrity": "sha512-1UdPmldjSGewOuWE40YYFZB1Q4im4LZoCMXGYeTeLz3R9hvxrDYJPRcPHXR4yBbubQebgGNCY2hwpJxmAiUMzQ==",
|
||||
"dependencies": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/osmtogeojson/node_modules/htmlparser2": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.5.1.tgz",
|
||||
"integrity": "sha512-9ouaQ6sjVJZS4NhPC65zNm2JCJotiH6BVm6iFvI90hRcsIEISMrgjqMUrPpU9G1VS4vTspH4dyaqSRf6JLQPbg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "1",
|
||||
"domhandler": "2.2",
|
||||
"domutils": "1.3",
|
||||
"readable-stream": "1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/osmtogeojson/node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"node_modules/osmtogeojson/node_modules/readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/osmtogeojson/node_modules/string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
},
|
||||
"node_modules/p-cancelable": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||
|
@ -16423,6 +16587,20 @@
|
|||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||
},
|
||||
"node_modules/tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
||||
},
|
||||
"node_modules/tiny-osmpbf": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-osmpbf/-/tiny-osmpbf-0.1.0.tgz",
|
||||
"integrity": "sha512-Sl0xuDdM0+bnrYPhTAWnQ5eui8+2cpYCnsBxq0EFR1/IgmfB7+FiC23I8aa7tdP4AjaWvBUMK34kfXdY6C1LCQ==",
|
||||
"dependencies": {
|
||||
"pbf": "^3.0.4",
|
||||
"tiny-inflate": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyqueue": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz",
|
||||
|
@ -17848,6 +18026,14 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/workbox-background-sync": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz",
|
||||
|
@ -20079,6 +20265,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
|
||||
},
|
||||
"@types/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-epMsEE85fi4lfmJUH/89/iV/LI+F5CvNIvmgs5g5jYFPfhO2S/ae8WSsLOKWdwtoaZw9Q2IhJ4tQ5tFCcS/4HA=="
|
||||
},
|
||||
"@types/eslint-visitor-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
|
||||
|
@ -20088,7 +20279,7 @@
|
|||
"version": "7946.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz",
|
||||
"integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.3",
|
||||
|
@ -20197,6 +20388,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz",
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw=="
|
||||
},
|
||||
"@types/osmtogeojson": {
|
||||
"version": "2.2.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/osmtogeojson/-/osmtogeojson-2.2.30.tgz",
|
||||
"integrity": "sha512-2TE8PjRSqDvlSCNUU3jln9KVKERXwOAVR7Yhxaww0ST9WSqylIAT0itivECUyvuK83/JkIsCnExS4yNobP8SVg=="
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -20602,6 +20798,11 @@
|
|||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
},
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.3.tgz",
|
||||
"integrity": "sha512-Lv2vySXypg4nfa51LY1nU8yDAGo/5YwF+EY/rUZgIbfvwVARcd67ttCM8SMsTeJy51YhHYavEq+FS6R0hW9PFQ=="
|
||||
},
|
||||
"@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
|
@ -22303,6 +22504,17 @@
|
|||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.0.2",
|
||||
"typedarray": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"confusing-browser-globals": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz",
|
||||
|
@ -22833,6 +23045,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
|
@ -24711,6 +24928,15 @@
|
|||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
|
||||
"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg=="
|
||||
},
|
||||
"geojson-numeric": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/geojson-numeric/-/geojson-numeric-0.2.1.tgz",
|
||||
"integrity": "sha512-rvItMp3W7pe16o2EQTnRw54v6WHdiE4bYjUsdr3FZskFb6oPC7gjLe4zginP+Wd1B/HLl2acTukfn16Lmwn7lg==",
|
||||
"requires": {
|
||||
"concat-stream": "2.0.0",
|
||||
"optimist": "~0.3.5"
|
||||
}
|
||||
},
|
||||
"geojson-vt": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz",
|
||||
|
@ -26467,6 +26693,27 @@
|
|||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
|
||||
"integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM="
|
||||
},
|
||||
"jsonparse": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz",
|
||||
"integrity": "sha512-fw7Q/8gFR8iSekUi9I+HqWIap6mywuoe7hQIg3buTVjuZgALKj4HAmm0X6f+TaL4c9NJbvyFQdaI2ppr5p6dnQ=="
|
||||
},
|
||||
"JSONStream": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.0.tgz",
|
||||
"integrity": "sha512-PiV28BpoUorz9kKFwRbD7+wg0t/k0ITHKn0DgCU44YZ/GaGAZRPt9q5PzoifC85gE55SEPIdMu0Labfxevj8cw==",
|
||||
"requires": {
|
||||
"jsonparse": "0.0.5",
|
||||
"through": "~2.2.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"through": {
|
||||
"version": "2.2.7",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.2.7.tgz",
|
||||
"integrity": "sha512-JIR0m0ybkmTcR8URann+HbwKmodP+OE8UCbsifQDYMLD5J3em1Cdn3MYPpbEd5elGDwmP98T+WbqP/tvzA5Mjg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
||||
|
@ -27828,6 +28075,14 @@
|
|||
"is-wsl": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"optimist": {
|
||||
"version": "0.3.7",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
|
||||
"integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==",
|
||||
"requires": {
|
||||
"wordwrap": "~0.0.2"
|
||||
}
|
||||
},
|
||||
"optimize-css-assets-webpack-plugin": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz",
|
||||
|
@ -27878,6 +28133,78 @@
|
|||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
|
||||
},
|
||||
"osm-polygon-features": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/osm-polygon-features/-/osm-polygon-features-0.9.2.tgz",
|
||||
"integrity": "sha512-5zNEFCq+G6X2TDkqbKYLF1+GtWVCCLA8zX+FVhSogsiTRsGquyaGRy5cYNW4BE3ci0MKOLvNTkFNsjsCNtgz0A=="
|
||||
},
|
||||
"osmtogeojson": {
|
||||
"version": "3.0.0-beta.5",
|
||||
"resolved": "https://registry.npmjs.org/osmtogeojson/-/osmtogeojson-3.0.0-beta.5.tgz",
|
||||
"integrity": "sha512-izvaUWnunrYvMB4LB0ZN15O1+g90c628yHS4SeSR3daVSBF9vdTHL7iVHfg0wEr1uEYjQ+lMJHCiYFusL5yKVg==",
|
||||
"requires": {
|
||||
"@mapbox/geojson-rewind": "0.5.2",
|
||||
"@types/geojson": "^7946.0",
|
||||
"@xmldom/xmldom": "0.8.3",
|
||||
"concat-stream": "2.0.0",
|
||||
"geojson-numeric": "0.2.1",
|
||||
"htmlparser2": "3.5.1",
|
||||
"JSONStream": "0.8.0",
|
||||
"optimist": "~0.3.5",
|
||||
"osm-polygon-features": "^0.9.1",
|
||||
"tiny-osmpbf": "^0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"domhandler": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.2.1.tgz",
|
||||
"integrity": "sha512-MFFBQFGkyTuNe3vL9WEw9JdlCwIoBYpOGESLeZAvc/jClYNsOl6P1KzevJbWg76GovdEycfR7/2/Ra7NnqtMKw==",
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.3.0.tgz",
|
||||
"integrity": "sha512-1UdPmldjSGewOuWE40YYFZB1Q4im4LZoCMXGYeTeLz3R9hvxrDYJPRcPHXR4yBbubQebgGNCY2hwpJxmAiUMzQ==",
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.5.1.tgz",
|
||||
"integrity": "sha512-9ouaQ6sjVJZS4NhPC65zNm2JCJotiH6BVm6iFvI90hRcsIEISMrgjqMUrPpU9G1VS4vTspH4dyaqSRf6JLQPbg==",
|
||||
"requires": {
|
||||
"domelementtype": "1",
|
||||
"domhandler": "2.2",
|
||||
"domutils": "1.3",
|
||||
"readable-stream": "1.1"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"p-cancelable": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
|
||||
|
@ -31440,6 +31767,20 @@
|
|||
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
|
||||
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q="
|
||||
},
|
||||
"tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="
|
||||
},
|
||||
"tiny-osmpbf": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-osmpbf/-/tiny-osmpbf-0.1.0.tgz",
|
||||
"integrity": "sha512-Sl0xuDdM0+bnrYPhTAWnQ5eui8+2cpYCnsBxq0EFR1/IgmfB7+FiC23I8aa7tdP4AjaWvBUMK34kfXdY6C1LCQ==",
|
||||
"requires": {
|
||||
"pbf": "^3.0.4",
|
||||
"tiny-inflate": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"tinyqueue": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz",
|
||||
|
@ -32588,6 +32929,11 @@
|
|||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw=="
|
||||
},
|
||||
"workbox-background-sync": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz",
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
"@mapbox/mapbox-gl-directions": "^4.1.1",
|
||||
"@mapbox/mapbox-gl-geocoder": "^5.0.1",
|
||||
"@svgr/webpack": "4.3.3",
|
||||
"@types/debounce": "^1.2.1",
|
||||
"@types/osmtogeojson": "^2.2.30",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
|
@ -17,6 +19,7 @@
|
|||
"camelcase": "^5.3.1",
|
||||
"case-sensitive-paths-webpack-plugin": "2.3.0",
|
||||
"css-loader": "3.4.2",
|
||||
"debounce": "^1.2.1",
|
||||
"dotenv": "8.2.0",
|
||||
"dotenv-expand": "5.1.0",
|
||||
"eslint": "^6.6.0",
|
||||
|
@ -38,6 +41,7 @@
|
|||
"mapbox-gl": "^2.9.2",
|
||||
"mini-css-extract-plugin": "0.9.0",
|
||||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"osmtogeojson": "^3.0.0-beta.5",
|
||||
"pnp-webpack-plugin": "1.6.4",
|
||||
"postcss-flexbugs-fixes": "4.1.0",
|
||||
"postcss-loader": "3.0.0",
|
||||
|
|
|
@ -104,7 +104,7 @@ h1 {
|
|||
bottom: 15px;
|
||||
}
|
||||
.mapboxgl-ctrl-top-left {
|
||||
margin-top: 50px;
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-geocoder--icon.mapboxgl-ctrl-geocoder--icon-search {
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import debounce from "debounce";
|
||||
import { LoadingStatusType, OverpassResponse} from "./interfaces";
|
||||
|
||||
import * as http from "https";
|
||||
import { addStreetLayers, removeStreetLayers } from "./drawing";
|
||||
import { safeCycleways } from "./overpass-requests";
|
||||
|
||||
import osmtogeojson from 'osmtogeojson';
|
||||
|
||||
/**
|
||||
* Make request to Overpass Turbo.
|
||||
* @param overpassQuery Overpass turbo query string
|
||||
* @returns
|
||||
*/
|
||||
export async function getOSMData(overpassQuery: string): Promise<OverpassResponse> {
|
||||
// overpass.kumi.systems
|
||||
// hostname: "overpass-api.de",
|
||||
const options = {
|
||||
hostname: "overpass.kumi.systems",
|
||||
port: 443,
|
||||
path: "/api/interpreter",
|
||||
method: "POST",
|
||||
headers: {
|
||||
// "Content-Type": "application/json",
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var req = http.request(options, function (res) {
|
||||
var body = "";
|
||||
res.setEncoding("utf8");
|
||||
res.on("data", (chunk) => (body += chunk));
|
||||
res.on("end", function () {
|
||||
if (res.statusCode !== 200) {
|
||||
console.log("error code", res.statusCode);
|
||||
reject(res.statusCode);
|
||||
}
|
||||
|
||||
const jsonResponse = JSON.parse(body);
|
||||
resolve(jsonResponse);
|
||||
});
|
||||
});
|
||||
req.on("error", function (e) {
|
||||
reject(e.message);
|
||||
});
|
||||
req.write(new URLSearchParams({
|
||||
'data': overpassQuery,
|
||||
}).toString());
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export const debouncedFetchAndDrawMarkers = debounce(fetchAndDrawMarkers, 2000);
|
||||
|
||||
async function fetchAndDrawMarkers(
|
||||
map: mapboxgl.Map,
|
||||
markers: React.MutableRefObject<mapboxgl.Marker[]>,
|
||||
setLoadingStatus: React.Dispatch<React.SetStateAction<LoadingStatusType>>
|
||||
) {
|
||||
setLoadingStatus("loading");
|
||||
const bounds = map.getBounds();
|
||||
const southernLat = bounds.getSouth();
|
||||
const westLong = bounds.getWest();
|
||||
const northLat = bounds.getNorth();
|
||||
const eastLong = bounds.getEast();
|
||||
|
||||
let safeRoutes: OverpassResponse;
|
||||
|
||||
const overpassBounds = [southernLat, westLong, northLat, eastLong];
|
||||
const boundsStr = overpassBounds.join(",");
|
||||
const safeRoutesOverpassQuery = safeCycleways(boundsStr);;
|
||||
|
||||
console.log("Started POST request...");
|
||||
try {
|
||||
safeRoutes = await getOSMData(safeRoutesOverpassQuery);
|
||||
} catch (e) {
|
||||
console.log("Error:", e);
|
||||
setLoadingStatus("unknownerror");
|
||||
return;
|
||||
}
|
||||
|
||||
const geoJson = osmtogeojson(safeRoutes, {})
|
||||
console.log(geoJson);
|
||||
console.log("Adding geojson to map...");
|
||||
|
||||
removeStreetLayers(map);
|
||||
addStreetLayers(map, geoJson);
|
||||
|
||||
setLoadingStatus("success");
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
import mapboxgl from "mapbox-gl";
|
||||
import {
|
||||
RawOverpassNode,
|
||||
} from "./interfaces";
|
||||
|
||||
import { FeatureCollection, Geometry, GeoJsonProperties, Feature, GeometryObject } from 'geojson';
|
||||
import { isGreenRoad, isOrangeRoad, isRedRoad } from "./osm-selectors";
|
||||
|
||||
export function drawMarkerAndCard(
|
||||
item: RawOverpassNode,
|
||||
map: mapboxgl.Map
|
||||
): mapboxgl.Marker {
|
||||
const { lat, lon } = item;
|
||||
|
||||
let markerOptions: mapboxgl.MarkerOptions = {};
|
||||
markerOptions.color = "gray";
|
||||
|
||||
const defaultScale = 0.5;
|
||||
markerOptions.scale = defaultScale;
|
||||
|
||||
if (item.tags && item.tags.capacity !== undefined) {
|
||||
const capacity = parseInt(item.tags.capacity);
|
||||
console.log({ capacity });
|
||||
let possibleScale = defaultScale + capacity / 30;
|
||||
if (possibleScale > 2) {
|
||||
possibleScale = 2;
|
||||
}
|
||||
markerOptions.scale = possibleScale;
|
||||
|
||||
if (item.tags && item.tags.covered === "yes") {
|
||||
markerOptions.color = "green";
|
||||
}
|
||||
if (item.tags && item.tags.lit === "yes") {
|
||||
markerOptions.color = "yellow";
|
||||
}
|
||||
if (item.tags && item.tags.bicycle_parking === "shed") {
|
||||
markerOptions.color = "#00ec18";
|
||||
}
|
||||
}
|
||||
|
||||
const marker = new mapboxgl.Marker(markerOptions)
|
||||
.setLngLat([lon, lat])
|
||||
.addTo(map);
|
||||
|
||||
if (window.orientation !== undefined) {
|
||||
marker.getElement().addEventListener("click", (e) => {
|
||||
map.flyTo({
|
||||
center: [lon, lat],
|
||||
});
|
||||
});
|
||||
}
|
||||
return marker;
|
||||
}
|
||||
|
||||
export function removeMarkers(markers: mapboxgl.Marker[]): void {
|
||||
markers.map((marker) => marker.remove());
|
||||
}
|
||||
export function removeStreetLayers(map: mapboxgl.Map): void {
|
||||
try {
|
||||
console.log("Removing sources...");
|
||||
map.removeLayer('greenRoadsId');
|
||||
map.removeLayer('redRoadsId');
|
||||
map.removeLayer('orangeRoadsId');
|
||||
|
||||
map.removeSource('greenRoads');
|
||||
map.removeSource('redRoads');
|
||||
map.removeSource('orangeRoads');
|
||||
} catch (e) {
|
||||
console.log("not removing sources - at least one doesn't exist yet");
|
||||
}
|
||||
}
|
||||
|
||||
export function addStreetLayers(map: mapboxgl.Map, geoJson: FeatureCollection<Geometry, GeoJsonProperties>) {
|
||||
/** Add below first vector layer */
|
||||
const layerToAddBefore = 'SharedUse';
|
||||
map.addSource('redRoads', {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
features: geoJson.features.filter(isRedRoad),
|
||||
type: "FeatureCollection"
|
||||
}
|
||||
});
|
||||
map.addSource('orangeRoads', {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
features: geoJson.features.filter(isOrangeRoad)
|
||||
,
|
||||
type: "FeatureCollection"
|
||||
}
|
||||
});
|
||||
|
||||
map.addSource('greenRoads', {
|
||||
type: 'geojson',
|
||||
data: {
|
||||
features: geoJson.features.filter(isGreenRoad)
|
||||
,
|
||||
type: "FeatureCollection"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Add a new layer to visualize the polygon.
|
||||
map.addLayer({
|
||||
'id': 'redRoadsId',
|
||||
'type': 'line',
|
||||
'source': 'redRoads', // reference the data source
|
||||
'layout': {},
|
||||
'paint': {
|
||||
"line-color": "red",
|
||||
"line-width": 3,
|
||||
'line-opacity': 0.3
|
||||
},
|
||||
}, layerToAddBefore);
|
||||
|
||||
map.addLayer({
|
||||
'id': 'orangeRoadsId',
|
||||
'type': 'line',
|
||||
'source': 'orangeRoads', // reference the data source
|
||||
'layout': {},
|
||||
'paint': {
|
||||
"line-color": "orange",
|
||||
"line-width": 3,
|
||||
'line-opacity': 0.5
|
||||
},
|
||||
}, layerToAddBefore);
|
||||
|
||||
|
||||
// Add a new layer to visualize the polygon.
|
||||
map.addLayer({
|
||||
'id': 'greenRoadsId',
|
||||
'type': 'line',
|
||||
'source': 'greenRoads', // reference the data source
|
||||
'layout': {},
|
||||
'paint': {
|
||||
"line-color": "#00FF00",
|
||||
"line-width": 7,
|
||||
'line-opacity': 0.8
|
||||
},
|
||||
}, layerToAddBefore);
|
||||
|
||||
}
|
||||
|
||||
export function drawMarkersAndCards(
|
||||
map: mapboxgl.Map,
|
||||
items: RawOverpassNode[]
|
||||
): mapboxgl.Marker[] {
|
||||
const markers = items
|
||||
.filter((item) => item.type === "node")
|
||||
.map((node: RawOverpassNode) => {
|
||||
return drawMarkerAndCard(node, map);
|
||||
});
|
||||
|
||||
return markers;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { RawOverpassNode, RawOverpassWay } from "./interfaces.js";
|
||||
|
||||
export function wayToNode(way: RawOverpassWay, allItems: (RawOverpassNode | RawOverpassWay)[]): RawOverpassNode | null {
|
||||
console.log("Converting way...");
|
||||
console.log(way)
|
||||
const nodes: number[] = way.nodes;
|
||||
const firstNodeId = nodes[0];
|
||||
const firstNode = allItems
|
||||
.find(item => item.type === 'node' && item.id === firstNodeId) as (RawOverpassNode | undefined)
|
||||
if (firstNode === undefined) {
|
||||
console.error(`Unable to find node with id ${firstNodeId}`);
|
||||
return null;
|
||||
}
|
||||
const representativeNode: RawOverpassNode = firstNode;
|
||||
const { lat, lon } = representativeNode;
|
||||
const { id, tags } = way;
|
||||
const output: RawOverpassNode = {
|
||||
id,
|
||||
lat,
|
||||
lon,
|
||||
type: 'node',
|
||||
tags
|
||||
}
|
||||
console.log({ output });
|
||||
return output;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
interface BicycleParkingInterface {
|
||||
amenity?: "bicycle_parking";
|
||||
capacity?: string;
|
||||
covered?: "yes" | "no";
|
||||
lit?: "yes" | "no";
|
||||
bicycle_parking?:
|
||||
| "stands"
|
||||
| "wall_loops"
|
||||
| "rack"
|
||||
| "safe_loops"
|
||||
| "shed"
|
||||
| "bollard"
|
||||
| "lockers"
|
||||
| "building";
|
||||
}
|
||||
|
||||
export interface RawOverpassNode {
|
||||
type: "node";
|
||||
id: number;
|
||||
lat: number;
|
||||
lon: number;
|
||||
tags?: BicycleParkingInterface;
|
||||
}
|
||||
export interface RawOverpassWay {
|
||||
type: "way";
|
||||
id: number;
|
||||
nodes: number[],
|
||||
tags?: BicycleParkingInterface;
|
||||
}
|
||||
|
||||
export type OverpassResponse = {
|
||||
elements: (RawOverpassNode | RawOverpassWay)[]
|
||||
};
|
||||
|
||||
|
||||
export type LoadingStatusType = "loading" | "success" | "429error" | "unknownerror" | "too_zoomed_out" | "ready_to_load"
|
|
@ -1,5 +1,6 @@
|
|||
// TODO: Find the layer of the road labels for the maptiler background
|
||||
const layerToAddAfter = undefined;
|
||||
// const layerToAddAfter = 'greenRoadsId'; //undefined;
|
||||
|
||||
function addLayer(
|
||||
map: mapboxgl.Map,
|
||||
|
@ -207,4 +208,9 @@ export const mapOnLoad = (map: mapboxgl.Map) => () => {
|
|||
},
|
||||
filter: ["==", "$type", "Polygon"],
|
||||
}, layerToAddAfter);
|
||||
|
||||
///////////////
|
||||
|
||||
|
||||
|
||||
};
|
95
src/map.tsx
95
src/map.tsx
|
@ -9,14 +9,23 @@ import "@mapbox/mapbox-gl-directions/dist/mapbox-gl-directions.css";
|
|||
|
||||
import MapboxGeocoder from "@mapbox/mapbox-gl-geocoder";
|
||||
import "@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css";
|
||||
import { debouncedFetchAndDrawMarkers } from "./api";
|
||||
import { LoadingStatusType } from "./interfaces";
|
||||
|
||||
const MAPBOX_TOKEN =
|
||||
"pk.eyJ1IjoiamFrZWMiLCJhIjoiY2tkaHplNGhjMDAyMDJybW4ybmRqbTBmMyJ9.AR_fnEuka8-cFb4Snp3upw";
|
||||
|
||||
const min_overpass_turbo_zoom = 15;
|
||||
/** Also the min zoom of the vector tileserver */
|
||||
// const max_overpass_turbo_zoom = 15;
|
||||
|
||||
mapboxgl.accessToken = MAPBOX_TOKEN;
|
||||
export function Map() {
|
||||
const mapContainer = React.useRef<HTMLDivElement>(null);
|
||||
const mapRef = React.useRef<mapboxgl.Map | null>(null);
|
||||
const markers = React.useRef<mapboxgl.Marker[]>([]);
|
||||
const [loadingStatus, setLoadingStatus] =
|
||||
useState<LoadingStatusType>("ready_to_load");
|
||||
|
||||
const [lng, setLng] = useState(151.2160755932166);
|
||||
const [lat, setLat] = useState(-33.88056647217827);
|
||||
|
@ -36,29 +45,7 @@ export function Map() {
|
|||
center: [lng, lat],
|
||||
zoom: zoom,
|
||||
hash: true,
|
||||
style: {
|
||||
version: 8,
|
||||
sources: {
|
||||
"raster-tiles": {
|
||||
type: "raster",
|
||||
tiles: [
|
||||
"https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png",
|
||||
],
|
||||
tileSize: 256,
|
||||
attribution:
|
||||
'© <a target="_blank" href="https://www.cyclosm.org">CyclOSM</a>, <a target="_blank" href="https://github.com/a-b-street/osm2streets">osm2streets</a>, <a target="_blank" href="https://openstreetmap.org/">OpenStreetMap contributors</a>',
|
||||
},
|
||||
},
|
||||
layers: [
|
||||
{
|
||||
id: "simple-tiles",
|
||||
type: "raster",
|
||||
source: "raster-tiles",
|
||||
minzoom: 0,
|
||||
maxzoom: 22,
|
||||
},
|
||||
],
|
||||
},
|
||||
style: "mapbox://styles/mapbox/dark-v11",
|
||||
});
|
||||
|
||||
const map = mapRef.current;
|
||||
|
@ -73,12 +60,12 @@ export function Map() {
|
|||
}),
|
||||
"top-left"
|
||||
);
|
||||
map.addControl(
|
||||
new MapboxDirections({
|
||||
accessToken: mapboxgl.accessToken,
|
||||
}),
|
||||
"top-left"
|
||||
);
|
||||
// map.addControl(
|
||||
// new MapboxDirections({
|
||||
// accessToken: mapboxgl.accessToken,
|
||||
// }),
|
||||
// "top-left"
|
||||
// );
|
||||
map.addControl(
|
||||
new mapboxgl.GeolocateControl({
|
||||
positionOptions: {
|
||||
|
@ -94,34 +81,70 @@ export function Map() {
|
|||
}
|
||||
const { lng, lat } = map.getCenter();
|
||||
const zoom = map.getZoom();
|
||||
if (zoom < min_overpass_turbo_zoom) {
|
||||
setLoadingStatus("too_zoomed_out");
|
||||
} else {
|
||||
setLoadingStatus("ready_to_load");
|
||||
}
|
||||
console.log(lng, lat, zoom);
|
||||
|
||||
setLng(map.getCenter().lng);
|
||||
setLat(map.getCenter().lat);
|
||||
setZoom(map.getZoom());
|
||||
});
|
||||
});
|
||||
|
||||
if (map.getZoom() < min_overpass_turbo_zoom) {
|
||||
setLoadingStatus("too_zoomed_out");
|
||||
} else {
|
||||
console.log(`zoom is ${map.getZoom()}`);
|
||||
debouncedFetchAndDrawMarkers(map, markers, setLoadingStatus);
|
||||
}
|
||||
|
||||
map.on("moveend", async () => {
|
||||
if (map === null) {
|
||||
return;
|
||||
}
|
||||
const zoom = map.getZoom();
|
||||
if (zoom > min_overpass_turbo_zoom) {
|
||||
console.log(`zoom is ${zoom}`);
|
||||
debouncedFetchAndDrawMarkers(map, markers, setLoadingStatus);
|
||||
}
|
||||
});
|
||||
});
|
||||
const statusMessages = {
|
||||
loading: "Loading safety ratings...",
|
||||
success: "Done loading safety ratings",
|
||||
ready_to_load: "About to load ratings...",
|
||||
too_zoomed_out: "Zoom in to see street safety ratings",
|
||||
unknownerror: "Error loading. Please wait a bit",
|
||||
"429error": "Too many requests, please try in a bit",
|
||||
};
|
||||
|
||||
const statusText = statusMessages[loadingStatus];
|
||||
return (
|
||||
<div>
|
||||
<div className="sidebar">
|
||||
<label>
|
||||
A work in progress side project by{" "}
|
||||
<span color="red">Warning:</span> Data is open source and not guaranteed to be
|
||||
accurate.
|
||||
<br></br>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://jakecoppinger.com/"
|
||||
href="https://github.com/jakecoppinger/safe-cycling-map/blob/main/docs/key.md"
|
||||
>
|
||||
Jake Coppinger
|
||||
</a>{" "}
|
||||
| Open source on{" "}
|
||||
View map key and how safety is calculated
|
||||
</a>
|
||||
<br></br>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/jakecoppinger/safe-cycling-map"
|
||||
>
|
||||
Github
|
||||
About this map
|
||||
</a>
|
||||
<br></br>
|
||||
{statusText}
|
||||
</label>
|
||||
</div>
|
||||
<div ref={mapContainer} className="map-container" />
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import { Geometry, GeoJsonProperties, Feature } from 'geojson';
|
||||
|
||||
/**
|
||||
* Is a red (dangerous) road if:
|
||||
* - Speed is higher than 40kmh
|
||||
* - Road is a residental street with default speed limit (50kph)
|
||||
* @param feature
|
||||
* @returns
|
||||
*/
|
||||
export function isRedRoad(feature: Feature<Geometry, GeoJsonProperties>): boolean {
|
||||
const p = feature.properties;
|
||||
if (p === null) {
|
||||
return false;
|
||||
}
|
||||
if(p.highway === 'primary' && p.maxspeed === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (p.maxspeed > 40) {
|
||||
return true;
|
||||
}
|
||||
if (p.highway === 'residential' && p.maxspeed === undefined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is an orange (caution) road if:
|
||||
* - Road has a speed limit less than 40kph and greater than 30kmh
|
||||
* - Has an on road, painted (non-separated) bike lane
|
||||
* @param feature
|
||||
* @returns
|
||||
*/
|
||||
export function isOrangeRoad(feature: Feature<Geometry, GeoJsonProperties>): boolean {
|
||||
const p = feature.properties;
|
||||
if (p === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p.maxspeed <= 40) {
|
||||
return true;
|
||||
}
|
||||
if (p.cycleway === 'lane') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is a green (safe) road if:
|
||||
* - Speed is less than or equal to 30kph
|
||||
* - Is a [living street](https://wiki.openstreetmap.org/wiki/Tag:highway%3Dliving_street)
|
||||
* - Is a separated cycleway
|
||||
* - Is a cycle lane separated from the road
|
||||
* - Is a shared path (bikes + pedestrians allowed)
|
||||
* @param feature
|
||||
* @returns
|
||||
*/
|
||||
export function isGreenRoad(feature: Feature<Geometry, GeoJsonProperties>): boolean {
|
||||
const p = feature.properties;
|
||||
if (p === null) {
|
||||
return false;
|
||||
}
|
||||
if (p.maxspeed <= 30) {
|
||||
return true;
|
||||
}
|
||||
if (p.highway === 'cycleway') {
|
||||
return true;
|
||||
}
|
||||
if (p.highway === 'shared_lane') {
|
||||
return true;
|
||||
}
|
||||
if (p.bicycle === 'designated' && p.highway === 'cycleway') {
|
||||
return true;
|
||||
}
|
||||
if (p.highway === 'living_street') {
|
||||
return true;
|
||||
}
|
||||
if (p.cycleway === 'track' || p['cycleway:left'] === 'track' || p['cycleway:right'] === 'track') {
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/** where boundsStr is in the format of `lat,lon,lat,lon` */
|
||||
export const bicycleParking = (boundsStr: string) => `
|
||||
[out:json][timeout:25];
|
||||
(
|
||||
// query part for: “bicycle_parking=*”
|
||||
node["bicycle_parking"](${boundsStr});
|
||||
way["bicycle_parking"](${boundsStr});
|
||||
relation["bicycle_parking"](${boundsStr});
|
||||
// query part for: “amenity=bicycle_parking”
|
||||
node["amenity"="bicycle_parking"](${boundsStr});
|
||||
way["amenity"="bicycle_parking"](${boundsStr});
|
||||
relation["amenity"="bicycle_parking"](${boundsStr});
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
`
|
||||
|
||||
/** where boundsStr is in the format of `lat,lon,lat,lon` */
|
||||
export const safeCycleways = (boundsStr: string) => `
|
||||
[out:json][timeout:60][bbox:${boundsStr}];
|
||||
|
||||
/* Select road types to display */
|
||||
|
||||
(
|
||||
way[highway];
|
||||
way["highway"="residential"];
|
||||
|
||||
way[highway=cycleway];
|
||||
way["highway"~"cycleway|path|footway|pedestrian"]["bicycle"~"yes|designated"];
|
||||
way[highway=proposed][proposed=cycleway];
|
||||
way[highway=construction][construction=cycleway];
|
||||
way[proposed=cycleway];
|
||||
way[cycleway=lane];
|
||||
way["cycleway:left"=track];
|
||||
way[cycleway=track];
|
||||
|
||||
);
|
||||
|
||||
// print results
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
`
|
Ładowanie…
Reference in New Issue