goog.provide('ol.format.TopoJSON'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.object'); goog.require('ol.Feature'); goog.require('ol.format.JSONFeature'); 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.proj'); /** * @constructor * @extends {ol.format.JSONFeature} * @param {olx.format.TopoJSONOptions=} opt_options Options. * @todo stability experimental */ ol.format.TopoJSON = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; goog.base(this); /** * @private * @type {ol.proj.Projection} */ this.defaultProjection_ = ol.proj.get(options.defaultProjection || 'EPSG:4326'); }; goog.inherits(ol.format.TopoJSON, ol.format.JSONFeature); /** * @const {Array.} * @private */ ol.format.TopoJSON.EXTENSIONS_ = ['.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 Array of arcs (already * transformed). * @return {Array.>} Coordinates array. * @private */ ol.format.TopoJSON.concatenateArcs_ = function(indices, arcs) { var coordinates = []; var index, arc; var i, ii; var j, jj; for (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); } // provide fresh copies of coordinate arrays for (j = 0, jj = coordinates.length; j < jj; ++j) { coordinates[j] = coordinates[j].slice(); } return coordinates; }; /** * Create a point from a TopoJSON geometry object. * * @param {TopoJSONGeometry} object TopoJSON object. * @param {Array.} scale Scale for each dimension. * @param {Array.} translate Translation for each dimension. * @return {ol.geom.Point} Geometry. * @private */ ol.format.TopoJSON.readPointGeometry_ = function(object, scale, translate) { var coordinates = object.coordinates; if (goog.isDef(scale) && goog.isDef(translate)) { ol.format.TopoJSON.transformVertex_(coordinates, scale, translate); } return new ol.geom.Point(coordinates); }; /** * Create a multi-point from a TopoJSON geometry object. * * @param {TopoJSONGeometry} object TopoJSON object. * @param {Array.} scale Scale for each dimension. * @param {Array.} translate Translation for each dimension. * @return {ol.geom.MultiPoint} Geometry. * @private */ ol.format.TopoJSON.readMultiPointGeometry_ = function(object, scale, translate) { var coordinates = object.coordinates; var i, ii; if (goog.isDef(scale) && goog.isDef(translate)) { for (i = 0, ii = coordinates.length; i < ii; ++i) { ol.format.TopoJSON.transformVertex_(coordinates[i], scale, translate); } } return new ol.geom.MultiPoint(coordinates); }; /** * Create a linestring from a TopoJSON geometry object. * * @param {TopoJSONGeometry} object TopoJSON object. * @param {Array.>} arcs Array of arcs. * @return {ol.geom.LineString} Geometry. * @private */ ol.format.TopoJSON.readLineStringGeometry_ = function(object, arcs) { var coordinates = ol.format.TopoJSON.concatenateArcs_(object.arcs, arcs); return new ol.geom.LineString(goog.array.flatten(coordinates)); }; /** * Create a multi-linestring from a TopoJSON geometry object. * * @param {TopoJSONGeometry} object TopoJSON object. * @param {Array.>} arcs Array of arcs. * @return {ol.geom.MultiLineString} Geometry. * @private */ ol.format.TopoJSON.readMultiLineStringGeometry_ = function(object, arcs) { var coordinates = []; var i, ii; for (i = 0, ii = object.arcs.length; i < ii; ++i) { coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); } return new ol.geom.MultiLineString(coordinates); }; /** * Create a polygon from a TopoJSON geometry object. * * @param {TopoJSONGeometry} object TopoJSON object. * @param {Array.>} arcs Array of arcs. * @return {ol.geom.Polygon} Geometry. * @private */ ol.format.TopoJSON.readPolygonGeometry_ = function(object, arcs) { var coordinates = []; var i, ii; for (i = 0, ii = object.arcs.length; i < ii; ++i) { coordinates[i] = ol.format.TopoJSON.concatenateArcs_(object.arcs[i], arcs); } return new ol.geom.Polygon(coordinates); }; /** * Create a multi-polygon from a TopoJSON geometry object. * * @param {TopoJSONGeometry} object TopoJSON object. * @param {Array.>} arcs Array of arcs. * @return {ol.geom.MultiPolygon} Geometry. * @private */ ol.format.TopoJSON.readMultiPolygonGeometry_ = function(object, arcs) { var coordinates = []; var polyArray, ringCoords, j, jj; var i, ii; for (i = 0, ii = object.arcs.length; i < ii; ++i) { // for each polygon polyArray = object.arcs[i]; ringCoords = []; for (j = 0, jj = polyArray.length; j < jj; ++j) { // for each ring ringCoords[j] = ol.format.TopoJSON.concatenateArcs_(polyArray[j], arcs); } coordinates[i] = ringCoords; } return new ol.geom.MultiPolygon(coordinates); }; /** * @inheritDoc */ ol.format.TopoJSON.prototype.getExtensions = function() { return ol.format.TopoJSON.EXTENSIONS_; }; /** * Create features from a TopoJSON GeometryCollection object. * * @param {TopoJSONGeometryCollection} collection TopoJSON Geometry * object. * @param {Array.>} arcs Array of arcs. * @param {Array.} scale Scale for each dimension. * @param {Array.} translate Translation for each dimension. * @return {Array.} Array of features. * @private */ ol.format.TopoJSON.readFeaturesFromGeometryCollection_ = function( collection, arcs, scale, translate) { var geometries = collection.geometries; var features = []; var i, ii; for (i = 0, ii = geometries.length; i < ii; ++i) { features[i] = ol.format.TopoJSON.readFeatureFromGeometry_( geometries[i], arcs, scale, translate); } return features; }; /** * 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. * @return {ol.Feature} Feature. * @private */ ol.format.TopoJSON.readFeatureFromGeometry_ = function(object, arcs, scale, translate) { var geometry; var type = object.type; var geometryReader = ol.format.TopoJSON.GEOMETRY_READERS_[type]; goog.asserts.assert(goog.isDef(geometryReader)); if ((type === 'Point') || (type === 'MultiPoint')) { geometry = geometryReader(object, scale, translate); } else { geometry = geometryReader(object, arcs); } var feature = new ol.Feature(); feature.setGeometry(geometry); if (goog.isDef(object.id)) { feature.setId(object.id); } if (goog.isDef(object.properties)) { feature.setValues(object.properties); } return feature; }; /** * @inheritDoc */ ol.format.TopoJSON.prototype.readFeaturesFromObject = function(object) { if (object.type == 'Topology') { var topoJSONTopology = /** @type {TopoJSONTopology} */ (object); var transform, scale = null, translate = null; if (goog.isDef(topoJSONTopology.transform)) { transform = /** @type {TopoJSONTransform} */ (topoJSONTopology.transform); scale = transform.scale; translate = transform.translate; } var arcs = topoJSONTopology.arcs; if (goog.isDef(transform)) { ol.format.TopoJSON.transformArcs_(arcs, scale, translate); } /** @type {Array.} */ var features = []; var topoJSONFeatures = goog.object.getValues(topoJSONTopology.objects); var i, ii; var feature; for (i = 0, ii = topoJSONFeatures.length; i < ii; ++i) { if (topoJSONFeatures[i].type === 'GeometryCollection') { feature = /** @type {TopoJSONGeometryCollection} */ (topoJSONFeatures[i]); features.push.apply(features, ol.format.TopoJSON.readFeaturesFromGeometryCollection_( feature, arcs, scale, translate)); } else { feature = /** @type {TopoJSONGeometry} */ (topoJSONFeatures[i]); features.push(ol.format.TopoJSON.readFeatureFromGeometry_( feature, arcs, scale, translate)); } } return features; } else { return []; } }; /** * 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.format.TopoJSON.transformArcs_ = function(arcs, scale, translate) { var i, ii; for (i = 0, ii = arcs.length; i < ii; ++i) { ol.format.TopoJSON.transformArc_(arcs[i], scale, translate); } }; /** * Apply a linear transform to an arc. The provided arc is modified in place. * * @param {Array.} arc Arc. * @param {Array.} scale Scale for each dimension. * @param {Array.} translate Translation for each dimension. * @private */ ol.format.TopoJSON.transformArc_ = function(arc, scale, translate) { var x = 0; var y = 0; var vertex; var i, ii; for (i = 0, ii = arc.length; i < ii; ++i) { vertex = arc[i]; x += vertex[0]; y += vertex[1]; vertex[0] = x; vertex[1] = y; ol.format.TopoJSON.transformVertex_(vertex, scale, translate); } }; /** * Apply a linear transform to a vertex. The provided vertex is modified in * place. * * @param {ol.Coordinate} vertex Vertex. * @param {Array.} scale Scale for each dimension. * @param {Array.} translate Translation for each dimension. * @private */ ol.format.TopoJSON.transformVertex_ = function(vertex, scale, translate) { vertex[0] = vertex[0] * scale[0] + translate[0]; vertex[1] = vertex[1] * scale[1] + translate[1]; }; /** * @inheritDoc */ ol.format.TopoJSON.prototype.readProjection = function(object) { return this.defaultProjection_; }; /** * @const * @private * @type {Object.} */ ol.format.TopoJSON.GEOMETRY_READERS_ = { 'Point': ol.format.TopoJSON.readPointGeometry_, 'LineString': ol.format.TopoJSON.readLineStringGeometry_, 'Polygon': ol.format.TopoJSON.readPolygonGeometry_, 'MultiPoint': ol.format.TopoJSON.readMultiPointGeometry_, 'MultiLineString': ol.format.TopoJSON.readMultiLineStringGeometry_, 'MultiPolygon': ol.format.TopoJSON.readMultiPolygonGeometry_ };