From 38172c4f20c6614c6df56df8bc4cb8af50457461 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Fri, 28 Jun 2013 15:54:31 -0600 Subject: [PATCH] Adding TopoJSON parser --- build.py | 1 + buildcfg/base.json | 1 + buildcfg/ol-all.json | 1 + buildcfg/ol-simple.json | 1 + buildcfg/ol-whitespace.json | 1 + buildcfg/ol.json | 1 + src/ol/parser/topojson.js | 370 ++++++++++++++++++++++++++++++++++++ 7 files changed, 376 insertions(+) create mode 100644 src/ol/parser/topojson.js diff --git a/build.py b/build.py index 1caf6e4b55..3f12710dab 100755 --- a/build.py +++ b/build.py @@ -312,6 +312,7 @@ def examples_star_json(name, match): '../externs/bingmaps.js', '../externs/bootstrap.js', '../externs/geojson.js', + '../externs/topojson.js', '../externs/oli.js', '../externs/proj4js.js', '../externs/tilejson.js', diff --git a/buildcfg/base.json b/buildcfg/base.json index 291e0e6268..4e3cb3da5a 100644 --- a/buildcfg/base.json +++ b/buildcfg/base.json @@ -42,6 +42,7 @@ "//json.js", "../externs/bingmaps.js", "../externs/geojson.js", + "../externs/topojson.js", "../externs/oli.js", "../externs/proj4js.js", "../externs/tilejson.js" diff --git a/buildcfg/ol-all.json b/buildcfg/ol-all.json index 340936188c..4d0e0b71b6 100644 --- a/buildcfg/ol-all.json +++ b/buildcfg/ol-all.json @@ -7,6 +7,7 @@ "../build/src/external/externs/types.js", "../externs/bingmaps.js", "../externs/geojson.js", + "../externs/topojson.js", "../externs/oli.js", "../externs/proj4js.js", "../externs/tilejson.js" diff --git a/buildcfg/ol-simple.json b/buildcfg/ol-simple.json index c21ae1528e..94f409c4f0 100644 --- a/buildcfg/ol-simple.json +++ b/buildcfg/ol-simple.json @@ -16,6 +16,7 @@ "//json.js", "../externs/bingmaps.js", "../externs/geojson.js", + "../externs/topojson.js", "../externs/oli.js", "../externs/proj4js.js", "../externs/tilejson.js" diff --git a/buildcfg/ol-whitespace.json b/buildcfg/ol-whitespace.json index fb99f44cc3..54cdbac634 100644 --- a/buildcfg/ol-whitespace.json +++ b/buildcfg/ol-whitespace.json @@ -17,6 +17,7 @@ "//json.js", "../externs/bingmaps.js", "../externs/geojson.js", + "../externs/topojson.js", "../externs/oli.js", "../externs/proj4js.js", "../externs/tilejson.js" diff --git a/buildcfg/ol.json b/buildcfg/ol.json index cb689bbddc..7cf458f1d7 100644 --- a/buildcfg/ol.json +++ b/buildcfg/ol.json @@ -17,6 +17,7 @@ "../build/src/external/externs/types.js", "../externs/bingmaps.js", "../externs/geojson.js", + "../externs/topojson.js", "../externs/oli.js", "../externs/proj4js.js", "../externs/tilejson.js" diff --git a/src/ol/parser/topojson.js b/src/ol/parser/topojson.js new file mode 100644 index 0000000000..88cfa7925c --- /dev/null +++ b/src/ol/parser/topojson.js @@ -0,0 +1,370 @@ +goog.provide('ol.parser.TopoJSON'); + +goog.require('ol.Feature'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.MultiPolygon'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.geom.Vertex'); +goog.require('ol.geom.VertexArray'); +goog.require('ol.parser.Parser'); +goog.require('ol.parser.ReadFeaturesOptions'); +goog.require('ol.parser.StringFeatureParser'); + + + +/** + * @constructor + * @implements {ol.parser.StringFeatureParser} + * @extends {ol.parser.Parser} + */ +ol.parser.TopoJSON = function() {}; +goog.inherits(ol.parser.TopoJSON, ol.parser.Parser); +goog.addSingletonGetter(ol.parser.TopoJSON); + + +/** + * Concatenate arcs into a coordinate array. + * @param {Array.} indices Indices of arcs to concatenate. Negative + * values indicate arcs need to be reversed. + * @param {Array.} arcs Arcs (already transformed). + * @return {ol.geom.VertexArray} Coordinate array. + * @private + */ +ol.parser.TopoJSON.prototype.concatenateArcs_ = function(indices, arcs) { + var coordinates = []; + var index, arc; + for (var i = 0, ii = indices.length; i < ii; ++i) { + index = indices[i]; + if (i > 0) { + // splicing together arcs, discard last point + coordinates.pop(); + } + if (index >= 0) { + // forward arc + arc = arcs[index]; + } else { + // reverse arc + arc = arcs[~index].slice().reverse(); + } + coordinates.push.apply(coordinates, arc); + } + return coordinates; +}; + + +/** + * Parse a TopoJSON string. + * @param {string} str TopoJSON string. + * @return {Array.} Array of features. + */ +ol.parser.TopoJSON.prototype.read = function(str) { + var topology = /** @type {TopoJSONTopology} */ (JSON.parse(str)); + return this.readFeaturesFromObject(topology); +}; + + +/** + * Create features from a TopoJSON topology string. + * + * @param {string} str TopoJSON topology string. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {Array.} Array of features. + */ +ol.parser.TopoJSON.prototype.readFeaturesFromString = + function(str, opt_options) { + var topology = /** @type {TopoJSONTopology} */ (JSON.parse(str)); + if (topology.type !== 'Topology') { + throw new Error('Not a "Topology" type object'); + } + return this.readFeaturesFromTopology_(topology, opt_options); +}; + + +/** + * Create features from a TopoJSON topology object. + * + * @param {TopoJSONTopology} topology TopoJSON topology object. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {Array.} Array of features. + */ +ol.parser.TopoJSON.prototype.readFeaturesFromObject = + function(topology, opt_options) { + if (topology.type !== 'Topology') { + throw new Error('Not a "Topology" type object'); + } + return this.readFeaturesFromTopology_(topology, opt_options); +}; + + +/** + * Create a feature from a TopoJSON geometry object. + * + * @param {TopoJSONGeometry} object TopoJSON geometry object. + * @param {Array.} arcs Array of arcs. + * @param {Array.} scale Scale for each dimension. + * @param {Array.} translate Translation for each dimension. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.Feature} Feature. + * @private + */ +ol.parser.TopoJSON.prototype.readFeatureFromGeometry_ = function(object, arcs, + scale, translate, opt_options) { + var geometry; + var type = object.type; + if (type === 'Point') { + geometry = this.readPoint_(/** @type {TopoJSONPoint} */ (object), scale, + translate, opt_options); + } else if (type === 'LineString') { + geometry = this.readLineString_(/** @type {TopoJSONLineString} */ (object), + arcs, opt_options); + } else if (type === 'Polygon') { + geometry = this.readPolygon_(/** @type {TopoJSONPolygon} */ (object), arcs, + opt_options); + } else if (type === 'MultiPoint') { + geometry = this.readMultiPoint_(/** @type {TopoJSONMultiPoint} */ (object), + scale, translate, opt_options); + } else if (type === 'MultiLineString') { + geometry = this.readMultiLineString_( + /** @type {TopoJSONMultiLineString} */(object), arcs, opt_options); + } else if (type === 'MultiPolygon') { + geometry = this.readMultiPolygon_( + /** @type {TopoJSONMultiPolygon} */ (object), arcs, opt_options); + } else { + throw new Error('Unsupported geometry type: ' + type); + } + var feature = new ol.Feature(); + feature.setGeometry(geometry); + if (goog.isDef(object.id)) { + feature.setFeatureId(String(object.id)); + } + return feature; +}; + + +/** + * @param {TopoJSONTopology} topology TopoJSON object. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {Array.} Parsed features. + * @private + */ +ol.parser.TopoJSON.prototype.readFeaturesFromTopology_ = function( + topology, opt_options) { + var transform = topology.transform; + var scale = transform.scale; + var translate = transform.translate; + var arcs = topology.arcs; + this.transformArcs_(scale, translate, arcs); + var objects = topology.objects; + var features = []; + for (var key in objects) { + features.push(this.readFeatureFromGeometry_(objects[key], arcs, scale, + translate, opt_options)); + } + return features; +}; + + +/** + * Create a linestring from a TopoJSON geometry object. + * + * @param {TopoJSONLineString} object TopoJSON object. + * @param {Array.} arcs Array of arcs. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.geom.LineString} Geometry. + * @private + */ +ol.parser.TopoJSON.prototype.readLineString_ = function(object, arcs, + opt_options) { + var coordinates = this.concatenateArcs_(object.arcs, arcs); + // TODO: make feature optional in callback + // TODO: get shared structure + return new ol.geom.LineString(coordinates); +}; + + +/** + * Create a multi-linestring from a TopoJSON geometry object. + * + * @param {TopoJSONMultiLineString} object TopoJSON object. + * @param {Array.} arcs Array of arcs. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.geom.MultiLineString} Geometry. + * @private + */ +ol.parser.TopoJSON.prototype.readMultiLineString_ = function(object, arcs, + opt_options) { + var array = object.arcs; // I'm out of good names + var num = array.length; + var coordinates = new Array(num); + for (var i = 0; i < num; ++i) { + coordinates[i] = this.concatenateArcs_(array[i], arcs); + } + // TODO: make feature optional in callback + // TODO: get shared structure + return new ol.geom.MultiLineString(coordinates); +}; + + +/** + * Create a multi-point from a TopoJSON geometry object. + * + * @param {TopoJSONMultiPoint} object TopoJSON object. + * @param {Array.} scale Scale for each dimension. + * @param {Array.} translate Translation for each dimension. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.geom.MultiPoint} Geometry. + * @private + */ +ol.parser.TopoJSON.prototype.readMultiPoint_ = function(object, scale, + translate, opt_options) { + var coordinates = object.coordinates; + for (var i = 0, ii = coordinates.length; i < ii; ++i) { + this.transformVertex_(coordinates[i], scale, translate); + } + // TODO: make feature optional in callback + // TODO: get shared structure + return new ol.geom.MultiPoint(coordinates); +}; + + +/** + * Create a multi-polygon from a TopoJSON geometry object. + * + * @param {TopoJSONMultiPolygon} object TopoJSON object. + * @param {Array.} arcs Array of arcs. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.geom.MultiPolygon} Geometry. + * @private + */ +ol.parser.TopoJSON.prototype.readMultiPolygon_ = function(object, arcs, + opt_options) { + var array = object.arcs; + var numPolys = array.length; + var coordinates = new Array(numPolys); + var polyArray, numRings, ringCoords, j; + for (var i = 0; i < numPolys; ++i) { + // for each polygon + polyArray = array[i]; + numRings = polyArray.length; + ringCoords = new Array(numRings); + for (j = 0; j < numRings; ++j) { + // for each ring + ringCoords[j] = this.concatenateArcs_(polyArray[j], arcs); + } + coordinates[i] = ringCoords; + } + // TODO: make feature optional in callback + // TODO: get shared structure + return new ol.geom.MultiPolygon(coordinates); +}; + + +/** + * Create a point from a TopoJSON geometry object. + * + * @param {TopoJSONPoint} object TopoJSON object. + * @param {Array.} scale Scale for each dimension. + * @param {Array.} translate Translation for each dimension. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.geom.Point} Geometry. + * @private + */ +ol.parser.TopoJSON.prototype.readPoint_ = function(object, scale, translate, + opt_options) { + var coordinates = object.coordinates; + this.transformVertex_(coordinates, scale, translate); + // TODO: make feature optional in callback + // TODO: get shared structure + return new ol.geom.Point(coordinates); +}; + + +/** + * Create a polygon from a TopoJSON geometry object. + * + * @param {TopoJSONPolygon} object TopoJSON object. + * @param {Array.} arcs Array of arcs. + * @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options. + * @return {ol.geom.Polygon} Geometry. + * @private + */ +ol.parser.TopoJSON.prototype.readPolygon_ = function(object, arcs, + opt_options) { + var array = object.arcs; // I'm out of good names + var num = array.length; + var coordinates = new Array(num); + for (var i = 0; i < num; ++i) { + coordinates[i] = this.concatenateArcs_(array[i], arcs); + } + // TODO: make feature optional in callback + // TODO: get shared structure + return new ol.geom.Polygon(coordinates); +}; + + +/** + * Apply a linear transform to array of arcs. The provided array of arcs is + * modified in place. + * + * @param {Array.} arcs Array of arcs. + * @param {Array.} scale Scale for each dimension. + * @param {Array.} translate Translation for each dimension. + * @private + */ +ol.parser.TopoJSON.prototype.transformArcs_ = function(arcs, scale, translate) { + for (var i = 0, ii = arcs.length; i < ii; ++i) { + this.transformArc_(arcs[i], scale, translate); + } +}; + + +/** + * Apply a linear transform to an arc. The provided arc is modified in place. + * + * @param {ol.geom.VertexArray} arc Arc. + * @param {Array.} scale Scale for each dimension. + * @param {Array.} translate Translation for each dimension. + * @private + */ +ol.parser.TopoJSON.prototype.transformArc_ = function(arc, scale, translate) { + var x = 0; + var y = 0; + var vertex; + for (var i = 0, ii = arc.length; i < ii; ++i) { + vertex = arc[i]; + x += vertex[0]; + y += vertex[1]; + vertex[0] = x; + vertex[1] = y; + this.transformVertex_(vertex, scale, translate); + } +}; + + +/** + * Apply a linear transform to a vertex. The provided vertex is modified in + * place. + * + * @param {ol.geom.Vertex} vertex Vertex. + * @param {Array.} scale Scale for each dimension. + * @param {Array.} translate Translation for each dimension. + * @private + */ +ol.parser.TopoJSON.prototype.transformVertex_ = function(vertex, scale, + translate) { + vertex[0] = vertex[0] * scale[0] + translate[0]; + vertex[1] = vertex[1] * scale[1] + translate[1]; +}; + + +/** + * Parse a TopoJSON string. + * @param {string} str TopoJSON string. + * @return {Array.} Array of features. + */ +ol.parser.TopoJSON.read = function(str) { + return ol.parser.TopoJSON.getInstance().read(str); +};