kopia lustrzana https://github.com/jakecoppinger/safe-cycling-map
Add bicycle parking loading from overpass API
rodzic
84a8f2b452
commit
0b9ffb2ed1
|
@ -12,6 +12,7 @@
|
|||
"@mapbox/mapbox-gl-directions": "^4.1.1",
|
||||
"@mapbox/mapbox-gl-geocoder": "^5.0.1",
|
||||
"@svgr/webpack": "4.3.3",
|
||||
"@types/debounce": "^1.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
|
@ -22,6 +23,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",
|
||||
|
@ -2396,6 +2398,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",
|
||||
|
@ -5741,6 +5748,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",
|
||||
|
@ -20079,6 +20091,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",
|
||||
|
@ -22833,6 +22850,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",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"@mapbox/mapbox-gl-directions": "^4.1.1",
|
||||
"@mapbox/mapbox-gl-geocoder": "^5.0.1",
|
||||
"@svgr/webpack": "4.3.3",
|
||||
"@types/debounce": "^1.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.10.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"babel-eslint": "10.1.0",
|
||||
|
@ -17,6 +18,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",
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import debounce from "debounce";
|
||||
import { OverpassResponse, RawOverpassNode } from "./interfaces";
|
||||
|
||||
import * as http from "https";
|
||||
import { drawMarkersAndCards, removeMarkers } from "./drawing";
|
||||
import { wayToNode } from "./geo-utils";
|
||||
// southern-most latitude, western-most longitude, northern-most latitude, eastern-most longitude.
|
||||
export async function getOSMData(bounds: number[]): Promise<OverpassResponse> {
|
||||
const options = {
|
||||
hostname: "overpass-api.de",
|
||||
port: 443,
|
||||
path: "/api/interpreter",
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
console.log("Started POST request...");
|
||||
const boundsStr = bounds.join(",");
|
||||
const request_str = `
|
||||
[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;
|
||||
`;
|
||||
console.log("request:", request_str);
|
||||
|
||||
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);
|
||||
const bars = jsonResponse.elements;
|
||||
resolve(bars);
|
||||
});
|
||||
});
|
||||
req.on("error", function (e) {
|
||||
reject(e.message);
|
||||
});
|
||||
req.write(request_str);
|
||||
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();
|
||||
const overpassBounds = [southernLat, westLong, northLat, eastLong];
|
||||
console.log("getting ads");
|
||||
|
||||
let ads: OverpassResponse;
|
||||
try {
|
||||
ads = await getOSMData(overpassBounds);
|
||||
} catch (e) {
|
||||
console.log("Error:", e);
|
||||
setLoadingStatus("unknownerror");
|
||||
return;
|
||||
}
|
||||
|
||||
removeMarkers(markers.current);
|
||||
|
||||
const nodesAndWayCenters: RawOverpassNode[] = ads
|
||||
.map((item) => (item.type === "way" ? wayToNode(item, ads) : item))
|
||||
.filter((item) => item !== null)
|
||||
.map((item) => item as RawOverpassNode)
|
||||
.filter((item) => item.tags !== undefined);
|
||||
|
||||
markers.current = await drawMarkersAndCards(map, nodesAndWayCenters);
|
||||
setLoadingStatus("success");
|
||||
}
|
||||
|
||||
type LoadingStatusType = "loading" | "success" | "429error" | "unknownerror";
|
|
@ -0,0 +1,68 @@
|
|||
import mapboxgl from "mapbox-gl";
|
||||
import {
|
||||
RawOverpassNode,
|
||||
} from "./interfaces";
|
||||
|
||||
|
||||
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 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,34 @@
|
|||
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 = (RawOverpassNode | RawOverpassWay)[];
|
||||
|
||||
|
||||
export type LoadingStatusType = "loading" | "success" | "429error" | "unknownerror";
|
28
src/map.tsx
28
src/map.tsx
|
@ -9,6 +9,8 @@ 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";
|
||||
|
@ -17,6 +19,9 @@ 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>("success");
|
||||
|
||||
const [lng, setLng] = useState(151.2160755932166);
|
||||
const [lat, setLat] = useState(-33.88056647217827);
|
||||
|
@ -100,12 +105,33 @@ export function Map() {
|
|||
setLat(map.getCenter().lat);
|
||||
setZoom(map.getZoom());
|
||||
});
|
||||
});
|
||||
debouncedFetchAndDrawMarkers(map, markers, setLoadingStatus);
|
||||
|
||||
|
||||
map.on("moveend", async () => {
|
||||
if (map === null) {
|
||||
return;
|
||||
}
|
||||
debouncedFetchAndDrawMarkers(map, markers, setLoadingStatus);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
const statusMessages = {
|
||||
loading: "Loading from OpenStreetMap...",
|
||||
success: "Done loading",
|
||||
unknownerror: "Error loading data. Please wait a bit",
|
||||
"429error": "Too many requests, please try in a bit",
|
||||
};
|
||||
|
||||
|
||||
const statusText = statusMessages[loadingStatus];
|
||||
return (
|
||||
<div>
|
||||
<div className="sidebar">
|
||||
<label>
|
||||
{" "}
|
||||
{statusText} |{" "}
|
||||
A work in progress side project by{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
|
|
Ładowanie…
Reference in New Issue