Skip to content

Instantly share code, notes, and snippets.

@akre54
Created September 7, 2016 13:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akre54/7dcb0a7f9fae27f177013a8778db59bb to your computer and use it in GitHub Desktop.
Save akre54/7dcb0a7f9fae27f177013a8778db59bb to your computer and use it in GitHub Desktop.
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