import * as gis from './gis.js' // /** @exports */ /** @module */ export function isGeojson(obj) { return typeof obj === 'object' && obj.type === 'FeatureCollection' } // Return deep copy of the given json file. export function clone(json) { return JSON.parse(JSON.stringify(json)) } export function minify(json) { if (typeof json === 'string') json = JSON.parse(json) const str = JSON.stringify(json) // compact form // newline for each feature return str.replace(/},{/g, '},\n\n{') } // ========== features export function bboxFeature(bbox, properties = {}) { const coords = gis.bboxCoords(bbox) coords.push(coords[0]) // polys are closed, repeat first coord return { type: 'Feature', geometry: { cordinates: coords, type: 'Polygon', }, properties, } } /** * Flatten MultiLineStrings to single LineStrings features. * @param {FeatureCollection|Array} geojson a FeatureCollection or Features array * @returns Features array */ export function flattenMultiLineStrings(geojson, cloneJson = true) { if (cloneJson) geojson = clone(geojson) const features = geojson.features || geojson const lineStrings = features.reduce((acc, obj) => { const geom = obj.geometry if (geom.type === 'LineString') { geom.coordinates.properties = obj.properties acc.push(geom.coordinates) } else if (geom.type === 'MultiLineString') { geom.coordinates.forEach(a => { a.properties = obj.properties acc.push(a) }) } return acc }, []) return lineStrings } // Convert LineStrings to Links. // Input can be a FeatureCollection or a Features array // Return an array of new Turtles & Links /** * @param {Model} model Instance of Model * @param {Array} bbox [west, south, east, north] Gis BBox * @param {FeatureCollection|Array} lineStrings geojson features * @returns {Turtles|Links} Return an array of new Turtles & Links */ export function lineStringsToLinks(model, bbox, lineStrings) { // const xfm = xfmFromBBox(model, bbox) const xfm = model.world.xfm || model.world.bboxTransform(...bbox) lineStrings = flattenMultiLineStrings(lineStrings) const nodeCache = {} const newTurtles = [] const newLinks = [] function getNode(pt) { const key = pt.toString() let node = nodeCache[key] if (node) return node node = model.turtles.createOne(t => { // t.setxy(...model.world.toWorld(...pt)) t.setxy(...xfm.toWorld(pt)) t.lon = pt[0] t.lat = pt[1] }) nodeCache[key] = node newTurtles.push(node) return node } function newLink(pt0, pt1) { const t0 = getNode(pt0) const t1 = getNode(pt1) const link = model.links.createOne(t0, t1) newLinks.push(link) return link } function lineStringToLinks(lineString) { lineString.reduce((acc, pt, i, a) => { const link = newLink(a[i - 1], pt) if (i === 1) { acc = [link] acc.properties = lineString.properties } else { acc.push(link) } link.lineString = acc return acc }) } lineStrings.forEach(lineString => lineStringToLinks(lineString)) return [newTurtles, newLinks] } // https://github.com/tmcw/geojson-flatten export function flatten(gj, cloneJson = true) { if (cloneJson) gj = clone(gj) switch ((gj && gj.type) || null) { case 'FeatureCollection': gj.features = gj.features.reduce(function (mem, feature) { return mem.concat(flatten(feature)) }, []) return gj case 'Feature': if (!gj.geometry) return [gj] return flatten(gj.geometry).map(function (geom) { var data = { type: 'Feature', properties: JSON.parse(JSON.stringify(gj.properties)), geometry: geom, } if (gj.id !== undefined) { data.id = gj.id } return data }) case 'MultiPoint': return gj.coordinates.map(function (_) { return { type: 'Point', coordinates: _ } }) case 'MultiPolygon': return gj.coordinates.map(function (_) { return { type: 'Polygon', coordinates: _ } }) case 'MultiLineString': return gj.coordinates.map(function (_) { return { type: 'LineString', coordinates: _ } }) case 'GeometryCollection': return gj.geometries.map(flatten).reduce(function (memo, geoms) { return memo.concat(geoms) }, []) case 'Point': case 'Polygon': case 'LineString': return [gj] } } // https://github.com/geosquare/geojson-bbox export function geojsonBBox(gj) { var coords, bbox if (!gj.hasOwnProperty('type')) return coords = getCoordinates(gj) bbox = [ Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, ] return coords.reduce(function (prev, coord) { return [ Math.min(coord[0], prev[0]), Math.min(coord[1], prev[1]), Math.max(coord[0], prev[2]), Math.max(coord[1], prev[3]), ] }, bbox) } export function getCoordinates(gj) { switch (gj.type) { case 'Point': return [gj.coordinates] case 'LineString': case 'MultiPoint': return gj.coordinates case 'Polygon': case 'MultiLineString': return gj.coordinates.reduce(function (dump, part) { return dump.concat(part) }, []) case 'MultiPolygon': return gj.coordinates.reduce(function (dump, poly) { return dump.concat( poly.reduce(function (points, part) { return points.concat(part) }, []) ) }, []) case 'Feature': return getCoordinates(gj.geometry) case 'GeometryCollection': return gj.geometries.reduce(function (dump, g) { return dump.concat(getCoordinates(g)) }, []) case 'FeatureCollection': return gj.features.reduce(function (dump, f) { return dump.concat(getCoordinates(f)) }, []) } }