-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
122 lines (107 loc) · 3.02 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"use strict";
const geojsonVt = require("geojson-vt");
const vtPbf = require("vt-pbf");
const request = require("requestretry");
const zlib = require("zlib");
const NodeCache = require( "node-cache" );
const url = process.env.PARK_API_URL || "https://api.parkendd.de/Ulm";
const getGeoJson = (url, callback) => {
request(
{
url: url,
maxAttempts: 20,
retryDelay: 30000,
retryStrategy: (err, response) =>
request.RetryStrategies.HTTPOrNetworkError(err, response) ||
(response && 202 === response.statusCode)
},
function(err, res, body) {
if (err) {
console.log(`Error when downloading ParkAPI data from ${url}: ${err} ${res} ${body}`);
callback(err);
return;
}
callback(null, parkApiToGeoJson(body));
}
);
};
// i haven't been able to find a way to directly generate the vector tiles, so
// we take a detour via geojson.
// if you know of a way to do it directly, let me know.
const parkApiToGeoJson = data => {
const json = JSON.parse(data);
const features = json.lots.map(lot => {
return {
type: "Feature",
geometry: {
type: "Point",
coordinates: [lot.coords.lng, lot.coords.lat]
},
// lat/long is a little superfluous, given that it's in the geometry above, but not removing it keeps the code simpler
properties: lot
};
});
return {
type: "FeatureCollection",
features: features
};
};
class ParkApiSource {
constructor(uri, callback) {
this.cacheKey = "tileindex";
this.cache = new NodeCache({ stdTTL: 60, useClones: false });
this.url = url;
callback(null, this);
}
fetchGeoJson(callback){
getGeoJson(this.url, (err, geojson) => {
if (err) {
callback(err);
return;
}
callback(geojson);
});
}
getTile(z, x, y, callback) {
if(this.cache.get(this.cacheKey)) {
const geojson = this.cache.get(this.cacheKey);
this.computeTile(geojson, z, x, y, callback);
} else {
this.fetchGeoJson((geojson) => {
this.cache.set(this.cacheKey, geojson);
this.computeTile(geojson, z, x, y, callback);
});
}
}
computeTile(geoJson, z, x, y, callback) {
const tileIndex = geojsonVt(geoJson, { maxZoom: 20, buffer: 512 });
let tile = tileIndex.getTile(z, x, y);
if (tile === null) {
tile = { features: [] };
}
const data = Buffer.from(vtPbf.fromGeojsonVt({ parking: tile }));
zlib.gzip(data, function(err, buffer) {
if (err) {
callback(err);
return;
}
callback(null, buffer, { "content-encoding": "gzip", "cache-control": "public,max-age=120" });
});
}
getInfo(callback) {
callback(null, {
format: "pbf",
maxzoom: 20,
vector_layers: [
{
description: "Parking lots data retrieved from a ParkAPI source",
id: "parking"
}
]
});
}
}
module.exports = ParkApiSource;
module.exports.registerProtocols = tilelive => {
tilelive.protocols["hbparking:"] = ParkApiSource;
};