Created
September 7, 2016 13:23
-
-
Save akre54/7dcb0a7f9fae27f177013a8778db59bb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import supercluster from 'supercluster' | |
import tilebelt from 'tilebelt' | |
import { max } from 'd3-array' | |
import db from './db' | |
import getMarkers from './markers' | |
const MIN_ZOOM = 1 | |
const MAX_STATE_ZOOM = 7 | |
const MAX_COUNTY_ZOOM = 9 | |
const MAX_CLUSTER_ZOOM = 17 | |
const MAX_ZOOM = 18 | |
let clusterer | |
const cache = {} | |
for (let z = MIN_ZOOM; z <= MAX_ZOOM; z++) { | |
cache[z] = {} | |
} | |
function loadClusterer() { | |
if (clusterer) return Promise.resolve(clusterer) | |
// Could potentially turn this into a MatView to speed up... (redo geocoded_nodeids) | |
const query = ` | |
SELECT nodeid, ST_Y(geom) AS lat, ST_X(geom) AS lng, name, type | |
FROM geocoded_nodeids | |
; | |
` | |
return db.fetch(query).then(points => { | |
points = points.map(p => ({ | |
type: 'Feature', | |
properties: { | |
nodeid: p.nodeid, | |
name: p.name, | |
type: p.type | |
}, | |
geometry: { | |
type: 'Point', | |
coordinates: [p.lng, p.lat] | |
} | |
})) | |
clusterer = supercluster({ | |
log: true, | |
radius: 150, | |
extent: 256, | |
maxZoom: MAX_CLUSTER_ZOOM, | |
minZoom: MAX_COUNTY_ZOOM | |
}) | |
clusterer.load(points) | |
for (let z = MAX_COUNTY_ZOOM; z <= MAX_CLUSTER_ZOOM; z++) { | |
cache[z].maxCount = max(clusterer.trees[z].points, d => d.numPoints) | |
} | |
return clusterer | |
}) | |
} | |
function cacheClusters(z, x, y, clusters) { | |
cache[z][x] || (cache[z][x] = {}) | |
cache[z][x][y] = clusters | |
return clusters | |
} | |
function loadClustersByTile(tile) { | |
const z = Number(tile.z) | |
const x = Number(tile.x) | |
const y = Number(tile.y) | |
// Only run clusterer on sub-county zoom levels | |
if (z > MAX_ZOOM || (z <= MAX_COUNTY_ZOOM && (!cache[z][x] || !cache[z][x][y]))) return Promise.resolve([]) | |
if (cache[z] && cache[z][x] && cache[z][x][y]) return Promise.resolve(cache[z][x][y]) | |
const bbox = tilebelt.tileToBBOX([x, y, z]) | |
if (z > MAX_CLUSTER_ZOOM) { | |
return subclusterTile(bbox) | |
.then(clusters => cacheClusters(z, x, y, clusters)) | |
} | |
return loadClusterer().then(clusterer => { | |
const clusters = clusterer.getClusters(bbox, z) | |
clusters.forEach(c => { | |
c.properties.maxCount = cache[z].maxCount | |
}) | |
return clusters | |
}).then(clusters => cacheClusters(z, x, y, clusters)) | |
} | |
function subclusterTile(bbox) { | |
const query = ` | |
SELECT nodeid, ST_Y(geom) AS lat, ST_X(geom) AS lng, name, type | |
FROM geocoded_nodeids | |
WHERE geom @ ST_MAKEENVELOPE(${bbox}); -- contained by (@) | |
` | |
return db.fetch(query).then(points => { | |
return points.map(p => { | |
return { | |
type: 'Feature', | |
properties: { | |
nodeid: p.nodeid, | |
name: p.name, | |
type: p.type | |
}, | |
geometry: { | |
type: 'Point', | |
coordinates: [p.lng, p.lat] | |
} | |
} | |
}) | |
}) | |
} | |
let states, counties | |
function loadStateAndCountyClusters() { | |
if (states && counties) return Promise.resolve({states: states, counties: counties}) | |
return getMarkers().then(points => { | |
states = points[0] | |
counties = points[1] | |
// TODO: dry up a bit | |
states.forEach(cluster => { | |
for (let z = MIN_ZOOM; z <= MAX_STATE_ZOOM; z++) { | |
const tile = tilebelt.pointToTile(cluster.lng, cluster.lat, z) | |
const x = tile[0] | |
const y = tile[1] | |
cache[z][x] || (cache[z][x] = {}) | |
cache[z][x][y] || (cache[z][x][y] = []) | |
cache[z][x][y].push(clusterToGeoJson(cluster)) | |
} | |
}) | |
counties.forEach(cluster => { | |
for (let z = MAX_STATE_ZOOM + 1; z <= MAX_COUNTY_ZOOM; z++) { | |
const tile = tilebelt.pointToTile(cluster.lng, cluster.lat, z) | |
const x = tile[0] | |
const y = tile[1] | |
cache[z] || (cache[z] = {}) | |
cache[z][x] || (cache[z][x] = {}) | |
cache[z][x][y] || (cache[z][x][y] = []) | |
cache[z][x][y].push(clusterToGeoJson(cluster)) | |
} | |
}) | |
}) | |
} | |
function clusterToGeoJson(point) { | |
return { | |
type: 'Feature', | |
geometry: { | |
type: 'Point', | |
coordinates: [point.lng, point.lat] | |
}, | |
properties: { | |
name: point.name, | |
point_count: point.count, | |
cluster: point.cluster, | |
maxCount: point.maxCount || point.maxcount | |
} | |
} | |
} | |
module.exports = loadClustersByTile | |
module.exports.loadStateAndCountyClusters = loadStateAndCountyClusters | |
module.exports.loadClusterer = loadClusterer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment