UNPKG

leaflet-mapbox-vector-tile

Version:

A Leaflet Plugin that renders Mapbox Vector Tiles on HTML5 Canvas.

1,766 lines (1,528 loc) 1.3 MB
/* The JSTS Topology Suite is a collection of JavaScript classes that implement the fundamental operations required to validate a given geo-spatial data set to a known topological specification. Copyright (C) 2011 The Authors This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* ====================================================================== jsts.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** @namespace */ jsts = { version: '0.13.4', /** @namespace */ algorithm: { /** @namespace */ distance: {}, /** @namespace */ locate: {} }, /** @namespace */ error: {}, /** @namespace */ geom: { /** @namespace */ util: {} }, /** @namespace */ geomgraph: { /** @namespace */ index: {} }, /** @namespace */ index: { /** @namespace */ bintree: {}, /** @namespace */ chain: {}, /** @namespace */ kdtree: {}, /** @namespace */ quadtree: {}, /** @namespace */ strtree: {} }, /** @namespace */ io: {}, /** @namespace */ noding: { /** @namespace */ snapround: {} }, /** @namespace */ operation: { /** @namespace */ buffer: {}, /** @namespace */ distance: {}, /** @namespace */ overlay: { /** @namespace */ snap: {} }, /** @namespace */ polygonize: {}, /** @namespace */ relate: {}, /** @namespace */ union: {}, /** @namespace */ valid: {} }, /** @namespace */ planargraph: {}, /** @namespace */ simplify: {}, /** @namespace */ triangulate: { /** @namespace */ quadedge: {} }, /** @namespace */ util: {} }; /** * Implement String.trim if native support is missing. */ if(typeof String.prototype.trim !== 'function') { String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); }; } /** * Global function intended for use as a generic abstract method. * @private */ jsts.abstractFunc = function() { throw new jsts.error.AbstractMethodInvocationError(); }; jsts.error = {}; /** * @constructor */ jsts.error.IllegalArgumentError = function(message) { this.name = 'IllegalArgumentError'; this.message = message; }; jsts.error.IllegalArgumentError.prototype = new Error(); /** * @constructor */ jsts.error.TopologyError = function(message, pt) { this.name = 'TopologyError'; this.message = pt ? message + ' [ ' + pt + ' ]' : message; }; jsts.error.TopologyError.prototype = new Error(); /** * @constructor */ jsts.error.AbstractMethodInvocationError = function() { this.name = 'AbstractMethodInvocationError'; this.message = 'Abstract method called, should be implemented in subclass.'; }; jsts.error.AbstractMethodInvocationError.prototype = new Error(); /** * @constructor */ jsts.error.NotImplementedError = function() { this.name = 'NotImplementedError'; this.message = 'This method has not yet been implemented.'; }; jsts.error.NotImplementedError.prototype = new Error(); /** * @constructor */ jsts.error.NotRepresentableError = function(message) { this.name = 'NotRepresentableError'; this.message = message; }; jsts.error.NotRepresentableError.prototype = new Error(); /** * @constructor message */ jsts.error.LocateFailureError = function(message) { this.name = 'LocateFailureError'; this.message = message; }; jsts.error.LocateFailureError.prototype = new Error(); if (typeof module !== "undefined") module.exports = jsts; /* ====================================================================== jsts/geom/GeometryFilter.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** * <code>GeometryCollection</code> classes support the concept of applying a * <code>GeometryFilter</code> to the <code>Geometry</code>. The filter is * applied to every element <code>Geometry</code>. A * <code>GeometryFilter</code> can either record information about the * <code>Geometry</code> or change the <code>Geometry</code> in some way. * <code>GeometryFilter</code> is an example of the Gang-of-Four Visitor * pattern. */ jsts.geom.GeometryFilter = function() { }; /** * Performs an operation with or on <code>geom</code>. * * @param {Geometry} * geom a <code>Geometry</code> to which the filter is applied. */ jsts.geom.GeometryFilter.prototype.filter = function(geom) { throw new jsts.error.AbstractMethodInvocationError(); }; /* ====================================================================== jsts/geom/util/PolygonExtracter.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** * @requires jsts/geom/GeometryFilter.js */ /** * Extracts all the {@link Polygon} elements from a {@link Geometry}. * * Constructs a PolygonExtracterFilter with a list in which to store Polygons * found. * * @param {Array} * comps * @extends {jsts.geom.GeometryFilter} * @see GeometryExtracter * @constructor */ jsts.geom.util.PolygonExtracter = function(comps) { this.comps = comps; }; jsts.geom.util.PolygonExtracter.prototype = new jsts.geom.GeometryFilter(); /** * @private */ jsts.geom.util.PolygonExtracter.prototype.comps = null; /** * Extracts the {@link Polygon} elements from a single {@link Geometry} and adds * them to the provided {@link List}. * * @param {jsts.geom.Geometry} * geom the geometry from which to extract. * @param {Array} * list the list to add the extracted elements to. * @return {Array} */ jsts.geom.util.PolygonExtracter.getPolygons = function(geom, list) { if (list === undefined) { list = []; } if (geom instanceof jsts.geom.Polygon) { list.push(geom); } else if (geom instanceof jsts.geom.GeometryCollection) { geom.apply(new jsts.geom.util.PolygonExtracter(list)); } // skip non-Polygonal elemental geometries return list; }; /** * @param {jsts.geom.Geometry} * geom */ jsts.geom.util.PolygonExtracter.prototype.filter = function(geom) { if (geom instanceof jsts.geom.Polygon) this.comps.push(geom); }; /* ====================================================================== jsts/io/WKTParser.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. * * This file incorporates work covered by the following copyright and * permission notice: * * Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the Clear BSD license. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ /** * Class for reading and writing Well-Known Text. * * NOTE: Adapted from OpenLayers 2.11 implementation. */ /** * Create a new parser for WKT * * @param {} * geometryFactory * @return An instance of WKTParser. */ jsts.io.WKTParser = function(geometryFactory) { this.geometryFactory = geometryFactory || new jsts.geom.GeometryFactory(); this.regExes = { 'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/, 'emptyTypeStr': /^\s*(\w+)\s*EMPTY\s*$/, 'spaces': /\s+/, 'parenComma': /\)\s*,\s*\(/, 'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/, // can't use {2} here 'trimParens': /^\s*\(?(.*?)\)?\s*$/ }; }; /** * Deserialize a WKT string and return a geometry. Supports WKT for POINT, * MULTIPOINT, LINESTRING, LINEARRING, MULTILINESTRING, POLYGON, MULTIPOLYGON, * and GEOMETRYCOLLECTION. * * @param {String} * wkt A WKT string. * @return {jsts.geom.Geometry} A geometry instance. */ jsts.io.WKTParser.prototype.read = function(wkt) { var geometry, type, str; wkt = wkt.replace(/[\n\r]/g, ' '); var matches = this.regExes.typeStr.exec(wkt); if (wkt.search('EMPTY') !== -1) { matches = this.regExes.emptyTypeStr.exec(wkt); matches[2] = undefined; } if (matches) { type = matches[1].toLowerCase(); str = matches[2]; if (this.parse[type]) { geometry = this.parse[type].apply(this, [str]); } } if (geometry === undefined) throw new Error('Could not parse WKT ' + wkt); return geometry; }; /** * Serialize a geometry into a WKT string. * * @param {jsts.geom.Geometry} * geometry A feature or array of features. * @return {String} The WKT string representation of the input geometries. */ jsts.io.WKTParser.prototype.write = function(geometry) { return this.extractGeometry(geometry); }; /** * Entry point to construct the WKT for a single Geometry object. * * @param {jsts.geom.Geometry} * geometry * * @return {String} A WKT string of representing the geometry. */ jsts.io.WKTParser.prototype.extractGeometry = function(geometry) { var type = geometry.CLASS_NAME.split('.')[2].toLowerCase(); if (!this.extract[type]) { return null; } var wktType = type.toUpperCase(); var data; if (geometry.isEmpty()) { data = wktType + ' EMPTY'; } else { data = wktType + '(' + this.extract[type].apply(this, [geometry]) + ')'; } return data; }; /** * Object with properties corresponding to the geometry types. Property values * are functions that do the actual data extraction. */ jsts.io.WKTParser.prototype.extract = { 'coordinate': function(coordinate) { return coordinate.x + ' ' + coordinate.y; }, /** * Return a space delimited string of point coordinates. * * @param {jsts.geom.Point} * point * @return {String} A string of coordinates representing the point. */ 'point': function(point) { return point.coordinate.x + ' ' + point.coordinate.y; }, /** * Return a comma delimited string of point coordinates from a multipoint. * * @param {jsts.geom.MultiPoint>} * multipoint * @return {String} A string of point coordinate strings representing the * multipoint. */ 'multipoint': function(multipoint) { var array = []; for ( var i = 0, len = multipoint.geometries.length; i < len; ++i) { array.push('(' + this.extract.point.apply(this, [multipoint.geometries[i]]) + ')'); } return array.join(','); }, /** * Return a comma delimited string of point coordinates from a line. * * @param {jsts.geom.LineString>} * linestring * @return {String} A string of point coordinate strings representing the * linestring. */ 'linestring': function(linestring) { var array = []; for ( var i = 0, len = linestring.points.length; i < len; ++i) { array.push(this.extract.coordinate.apply(this, [linestring.points[i]])); } return array.join(','); }, /** * Return a comma delimited string of linestring strings from a * multilinestring. * * @param {jsts.geom.MultiLineString>} * multilinestring * @return {String} A string of of linestring strings representing the * multilinestring. */ 'multilinestring': function(multilinestring) { var array = []; for ( var i = 0, len = multilinestring.geometries.length; i < len; ++i) { array.push('(' + this.extract.linestring.apply(this, [multilinestring.geometries[i]]) + ')'); } return array.join(','); }, /** * Return a comma delimited string of linear ring arrays from a polygon. * * @param {jsts.geom.Polygon>} * polygon * @return {String} An array of linear ring arrays representing the polygon. */ 'polygon': function(polygon) { var array = []; array.push('(' + this.extract.linestring.apply(this, [polygon.shell]) + ')'); for ( var i = 0, len = polygon.holes.length; i < len; ++i) { array.push('(' + this.extract.linestring.apply(this, [polygon.holes[i]]) + ')'); } return array.join(','); }, /** * Return an array of polygon arrays from a multipolygon. * * @param {jsts.geom.MultiPolygon>} * multipolygon * @return {String} An array of polygon arrays representing the multipolygon. */ 'multipolygon': function(multipolygon) { var array = []; for ( var i = 0, len = multipolygon.geometries.length; i < len; ++i) { array.push('(' + this.extract.polygon.apply(this, [multipolygon.geometries[i]]) + ')'); } return array.join(','); }, /** * Return the WKT portion between 'GEOMETRYCOLLECTION(' and ')' for an * geometrycollection. * * @param {jsts.geom.GeometryCollection>} * collection * @return {String} internal WKT representation of the collection. */ 'geometrycollection': function(collection) { var array = []; for ( var i = 0, len = collection.geometries.length; i < len; ++i) { array.push(this.extractGeometry.apply(this, [collection.geometries[i]])); } return array.join(','); } }; /** * Object with properties corresponding to the geometry types. Property values * are functions that do the actual parsing. */ jsts.io.WKTParser.prototype.parse = { /** * Return point geometry given a point WKT fragment. * * @param {String} * str A WKT fragment representing the point. * @return {jsts.geom.Point} A point geometry. * @private */ 'point': function(str) { if (str === undefined) { return this.geometryFactory.createPoint(null); } var coords = str.trim().split(this.regExes.spaces); return this.geometryFactory.createPoint(new jsts.geom.Coordinate(coords[0], coords[1])); }, /** * Return a multipoint geometry given a multipoint WKT fragment. * * @param {String} * A WKT fragment representing the multipoint. * @return {jsts.geom.Point} A multipoint feature. * @private */ 'multipoint': function(str) { if (str === undefined) { return this.geometryFactory.createMultiPoint(null); } var point; var points = str.trim().split(','); var components = []; for ( var i = 0, len = points.length; i < len; ++i) { point = points[i].replace(this.regExes.trimParens, '$1'); components.push(this.parse.point.apply(this, [point])); } return this.geometryFactory.createMultiPoint(components); }, /** * Return a linestring geometry given a linestring WKT fragment. * * @param {String} * A WKT fragment representing the linestring. * @return {jsts.geom.LineString} A linestring geometry. * @private */ 'linestring': function(str) { if (str === undefined) { return this.geometryFactory.createLineString(null); } var points = str.trim().split(','); var components = []; var coords; for ( var i = 0, len = points.length; i < len; ++i) { coords = points[i].trim().split(this.regExes.spaces); components.push(new jsts.geom.Coordinate(coords[0], coords[1])); } return this.geometryFactory.createLineString(components); }, /** * Return a linearring geometry given a linearring WKT fragment. * * @param {String} * A WKT fragment representing the linearring. * @return {jsts.geom.LinearRing} A linearring geometry. * @private */ 'linearring': function(str) { if (str === undefined) { return this.geometryFactory.createLinearRing(null); } var points = str.trim().split(','); var components = []; var coords; for ( var i = 0, len = points.length; i < len; ++i) { coords = points[i].trim().split(this.regExes.spaces); components.push(new jsts.geom.Coordinate(coords[0], coords[1])); } return this.geometryFactory.createLinearRing(components); }, /** * Return a multilinestring geometry given a multilinestring WKT fragment. * * @param {String} * A WKT fragment representing the multilinestring. * @return {jsts.geom.MultiLineString} A multilinestring geometry. * @private */ 'multilinestring': function(str) { if (str === undefined) { return this.geometryFactory.createMultiLineString(null); } var line; var lines = str.trim().split(this.regExes.parenComma); var components = []; for ( var i = 0, len = lines.length; i < len; ++i) { line = lines[i].replace(this.regExes.trimParens, '$1'); components.push(this.parse.linestring.apply(this, [line])); } return this.geometryFactory.createMultiLineString(components); }, /** * Return a polygon geometry given a polygon WKT fragment. * * @param {String} * A WKT fragment representing the polygon. * @return {jsts.geom.Polygon} A polygon geometry. * @private */ 'polygon': function(str) { if (str === undefined) { return this.geometryFactory.createPolygon(null); } var ring, linestring, linearring; var rings = str.trim().split(this.regExes.parenComma); var shell; var holes = []; for ( var i = 0, len = rings.length; i < len; ++i) { ring = rings[i].replace(this.regExes.trimParens, '$1'); linestring = this.parse.linestring.apply(this, [ring]); linearring = this.geometryFactory.createLinearRing(linestring.points); if (i === 0) { shell = linearring; } else { holes.push(linearring); } } return this.geometryFactory.createPolygon(shell, holes); }, /** * Return a multipolygon geometry given a multipolygon WKT fragment. * * @param {String} * A WKT fragment representing the multipolygon. * @return {jsts.geom.MultiPolygon} A multipolygon geometry. * @private */ 'multipolygon': function(str) { if (str === undefined) { return this.geometryFactory.createMultiPolygon(null); } var polygon; var polygons = str.trim().split(this.regExes.doubleParenComma); var components = []; for ( var i = 0, len = polygons.length; i < len; ++i) { polygon = polygons[i].replace(this.regExes.trimParens, '$1'); components.push(this.parse.polygon.apply(this, [polygon])); } return this.geometryFactory.createMultiPolygon(components); }, /** * Return a geometrycollection given a geometrycollection WKT fragment. * * @param {String} * A WKT fragment representing the geometrycollection. * @return {jsts.geom.GeometryCollection} * @private */ 'geometrycollection': function(str) { if (str === undefined) { return this.geometryFactory.createGeometryCollection(null); } // separate components of the collection with | str = str.replace(/,\s*([A-Za-z])/g, '|$1'); var wktArray = str.trim().split('|'); var components = []; for ( var i = 0, len = wktArray.length; i < len; ++i) { components.push(jsts.io.WKTParser.prototype.read.apply(this, [wktArray[i]])); } return this.geometryFactory.createGeometryCollection(components); } }; /* ====================================================================== jsts/index/ItemVisitor.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** * A visitor for items in an index. * * @interface */ jsts.index.ItemVisitor = function() { }; /** * @param {Object} item * @public */ jsts.index.ItemVisitor.prototype.visitItem = function() { throw new jsts.error.AbstractMethodInvocationError(); }; /* ====================================================================== jsts/algorithm/CGAlgorithms.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** * Specifies and implements various fundamental Computational Geometric * algorithms. The algorithms supplied in this class are robust for * double-precision floating point. * * @constructor */ jsts.algorithm.CGAlgorithms = function() { }; /** * A value that indicates an orientation of clockwise, or a right turn. */ jsts.algorithm.CGAlgorithms.CLOCKWISE = -1; /** * A value that indicates an orientation of clockwise, or a right turn. */ jsts.algorithm.CGAlgorithms.RIGHT = jsts.algorithm.CGAlgorithms.CLOCKWISE; /** * A value that indicates an orientation of counterclockwise, or a left turn. */ jsts.algorithm.CGAlgorithms.COUNTERCLOCKWISE = 1; /** * A value that indicates an orientation of counterclockwise, or a left turn. */ jsts.algorithm.CGAlgorithms.LEFT = jsts.algorithm.CGAlgorithms.COUNTERCLOCKWISE; /** * A value that indicates an orientation of collinear, or no turn (straight). */ jsts.algorithm.CGAlgorithms.COLLINEAR = 0; /** * A value that indicates an orientation of collinear, or no turn (straight). */ jsts.algorithm.CGAlgorithms.STRAIGHT = jsts.algorithm.CGAlgorithms.COLLINEAR; /** * Returns the index of the direction of the point <code>q</code> relative to * a vector specified by <code>p1-p2</code>. * * @param {jsts.geom.Coordinate} * p1 the origin point of the vector. * @param {jsts.geom.Coordinate} * p2 the final point of the vector. * @param {jsts.geom.Coordinate} * q the point to compute the direction to. * * @return {Number} 1 if q is counter-clockwise (left) from p1-p2. * @return {Number} -1 if q is clockwise (right) from p1-p2. * @return {Number} 0 if q is collinear with p1-p2. */ jsts.algorithm.CGAlgorithms.orientationIndex = function(p1, p2, q) { /** * MD - 9 Aug 2010 It seems that the basic algorithm is slightly orientation * dependent, when computing the orientation of a point very close to a line. * This is possibly due to the arithmetic in the translation to the origin. * * For instance, the following situation produces identical results in spite * of the inverse orientation of the line segment: * * Coordinate p0 = new Coordinate(219.3649559090992, 140.84159161824724); * Coordinate p1 = new Coordinate(168.9018919682399, -5.713787599646864); * * Coordinate p = new Coordinate(186.80814046338352, 46.28973405831556); int * orient = orientationIndex(p0, p1, p); int orientInv = orientationIndex(p1, * p0, p); * * A way to force consistent results is to normalize the orientation of the * vector using the following code. However, this may make the results of * orientationIndex inconsistent through the triangle of points, so it's not * clear this is an appropriate patch. * */ var dx1, dy1, dx2, dy2; dx1 = p2.x - p1.x; dy1 = p2.y - p1.y; dx2 = q.x - p2.x; dy2 = q.y - p2.y; return jsts.algorithm.RobustDeterminant.signOfDet2x2(dx1, dy1, dx2, dy2); }; /** * Tests whether a point lies inside or on a ring. The ring may be oriented in * either direction. A point lying exactly on the ring boundary is considered to * be inside the ring. * <p> * This method does <i>not</i> first check the point against the envelope of * the ring. * * @param {jsts.geom.Coordinate} * p point to check for ring inclusion. * @param {Array{jsts.geom.Coordinate}} * ring an array of coordinates representing the ring (which must have * first point identical to last point) * @return {Boolean} true if p is inside ring. * * @see locatePointInRing */ jsts.algorithm.CGAlgorithms.isPointInRing = function(p, ring) { return jsts.algorithm.CGAlgorithms.locatePointInRing(p, ring) !== jsts.geom.Location.EXTERIOR; }; /** * Determines whether a point lies in the interior, on the boundary, or in the * exterior of a ring. The ring may be oriented in either direction. * <p> * This method does <i>not</i> first check the point against the envelope of * the ring. * * @param {jsts.geom.Coordinate} * p point to check for ring inclusion. * @param {Array{jsts.geom.Coordinate}} * ring an array of coordinates representing the ring (which must have * first point identical to last point) * @return {jsts.geom.Location} the {@link Location} of p relative to the ring. */ jsts.algorithm.CGAlgorithms.locatePointInRing = function(p, ring) { return jsts.algorithm.RayCrossingCounter.locatePointInRing(p, ring); }; /** * Tests whether a point lies on the line segments defined by a list of * coordinates. * * @param {jsts.geom.Coordinate} * p the coordinate to test. * @param {Array{jsts.geom.Coordinate}} * pt An array of coordinates defining line segments * @return {Boolean} true if the point is a vertex of the line or lies in the * interior of a line segment in the linestring. */ jsts.algorithm.CGAlgorithms.isOnLine = function(p, pt) { var lineIntersector, i, il, p0, p1; lineIntersector = new jsts.algorithm.RobustLineIntersector(); for (i = 1, il = pt.length; i < il; i++) { p0 = pt[i - 1]; p1 = pt[i]; lineIntersector.computeIntersection(p, p0, p1); if (lineIntersector.hasIntersection()) { return true; } } return false; }; /** * Computes whether a ring defined by an array of {@link Coordinate}s is * oriented counter-clockwise. * <ul> * <li>The list of points is assumed to have the first and last points equal. * <li>This will handle coordinate lists which contain repeated points. * </ul> * This algorithm is <b>only</b> guaranteed to work with valid rings. If the * ring is invalid (e.g. self-crosses or touches), the computed result may not * be correct. * * @param {Array{jsts.geom.Coordinate}} * ring an array of Coordinates forming a ring * @return {Boolean} true if the ring is oriented counter-clockwise. * @throws IllegalArgumentException * if there are too few points to determine orientation (< 3) */ jsts.algorithm.CGAlgorithms.isCCW = function(ring) { var nPts, hiPt, hiIndex, p, iPrev, iNext, prev, next, i, disc, isCCW; // # of points without closing endpoint nPts = ring.length - 1; // sanity check if (nPts < 3) { throw new jsts.IllegalArgumentError( 'Ring has fewer than 3 points, so orientation cannot be determined'); } // find highets point hiPt = ring[0]; hiIndex = 0; i = 1; for (i; i <= nPts; i++) { p = ring[i]; if (p.y > hiPt.y) { hiPt = p; hiIndex = i; } } // find distinct point before highest point iPrev = hiIndex; do { iPrev = iPrev - 1; if (iPrev < 0) { iPrev = nPts; } } while (ring[iPrev].equals2D(hiPt) && iPrev !== hiIndex); // find distinct point after highest point iNext = hiIndex; do { iNext = (iNext + 1) % nPts; } while (ring[iNext].equals2D(hiPt) && iNext !== hiIndex); prev = ring[iPrev]; next = ring[iNext]; /** * This check catches cases where the ring contains an A-B-A configuration of * points. This can happen if the ring does not contain 3 distinct points * (including the case where the input array has fewer than 4 elements), or it * contains coincident line segments. */ if (prev.equals2D(hiPt) || next.equals2D(hiPt) || prev.equals2D(next)) { return false; } disc = jsts.algorithm.CGAlgorithms.computeOrientation(prev, hiPt, next); /** * If disc is exactly 0, lines are collinear. There are two possible cases: * (1) the lines lie along the x axis in opposite directions (2) the lines lie * on top of one another * * (1) is handled by checking if next is left of prev ==> CCW (2) will never * happen if the ring is valid, so don't check for it (Might want to assert * this) */ isCCW = false; if (disc === 0) { // poly is CCW if prev x is right of next x isCCW = (prev.x > next.x); } else { // if area is positive, points are ordered CCW isCCW = (disc > 0); } return isCCW; }; /** * Computes the orientation of a point q to the directed line segment p1-p2. The * orientation of a point relative to a directed line segment indicates which * way you turn to get to q after travelling from p1 to p2. * * @param {jsts.geom.Coordinate} * p1 First coordinate of the linesegment. * @param {jsts.geom.Coordinate} * p2 Second coordinate of the linesegment. * @param {jsts.geom.Coordinate} * q The point to calculate orientation of. * * @return {Number} 1 if q is counter-clockwise from p1-p2. * @return {Number} -1 if q is clockwise from p1-p2. * @return {Number} 0 if q is collinear with p1-p2. */ jsts.algorithm.CGAlgorithms.computeOrientation = function(p1, p2, q) { return jsts.algorithm.CGAlgorithms.orientationIndex(p1, p2, q); }; /** * Computes the distance from a point p to a line segment AB * * Note: NON-ROBUST! * * @param {jsts.geom.Coordinate} * p the point to compute the distance for. * @param {jsts.geom.Coordinate} * A one point of the line. * @param {jsts.geom.Coordinate} * B another point of the line (must be different to A). * @return {Number} the distance from p to line segment AB. */ jsts.algorithm.CGAlgorithms.distancePointLine = function(p, A, B) { if (!(A instanceof jsts.geom.Coordinate)) { jsts.algorithm.CGAlgorithms.distancePointLine2.apply(this, arguments); } // if start = end, then just compute distance to one of the endpoints if (A.x === B.x && A.y === B.y) { return p.distance(A); } // otherwise use comp.graphics.algorithms Frequently Asked Questions method /*(1) AC dot AB r = --------- ||AB||^2 r has the following meaning: r=0 P = A r=1 P = B r<0 P is on the backward extension of AB r>1 P is on the forward extension of AB 0<r<1 P is interior to AB */ var r, s; r = ((p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); if (r <= 0.0) { return p.distance(A); } if (r >= 1.0) { return p.distance(B); } /*(2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- L^2 Then the distance from C to P = |s|*L. */ s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); return Math.abs(s) * Math.sqrt(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))); }; /** * Computes the perpendicular distance from a point p to the (infinite) line * containing the points AB * * @param {jsts.geom.Coordinate} * p the point to compute the distance for. * @param {jsts.geom.Coordinate} * A one point of the line. * @param {jsts.geom.Coordinate} * B another point of the line (must be different to A). * @return {Number} the distance from p to line AB. */ jsts.algorithm.CGAlgorithms.distancePointLinePerpendicular = function(p, A, B) { // use comp.graphics.algorithms Frequently Asked Questions method /*(2) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- L^2 Then the distance from C to P = |s|*L. */ var s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) / ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y)); return Math.abs(s) * Math.sqrt(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))); }; /** * Computes the distance from a point to a sequence of line segments. * * @param {jsts.geom.Coordinate} * p a point. * @param {Array{jsts.geom.Coordinate}} * line a sequence of contiguous line segments defined by their * vertices * @return {Number} the minimum distance between the point and the line * segments. */ jsts.algorithm.CGAlgorithms.distancePointLine2 = function(p, line) { var minDistance, i, il, dist; if (line.length === 0) { throw new jsts.error.IllegalArgumentError( 'Line array must contain at least one vertex'); } minDistance = p.distance(line[0]); for (i = 0, il = line.length - 1; i < il; i++) { dist = jsts.algorithm.CGAlgorithms.distancePointLine(p, line[i], line[i + 1]); if (dist < minDistance) { minDistance = dist; } } return minDistance; }; /** * Computes the distance from a line segment AB to a line segment CD * * Note: NON-ROBUST! * * @param {jsts.geom.Coordinate} * A a point of one line. * @param {jsts.geom.Coordinate} * B the second point of (must be different to A). * @param {jsts.geom.Coordinate} * C one point of the line. * @param {jsts.geom.Coordinate} * D another point of the line (must be different to A). * @return {Number} the distance. */ jsts.algorithm.CGAlgorithms.distanceLineLine = function(A, B, C, D) { // check for zero-length segments if (A.equals(B)) { return jsts.algorithm.CGAlgorithms.distancePointLine(A, C, D); } if (C.equals(D)) { return jsts.algorithm.CGAlgorithms.distancePointLine(D, A, B); } // AB and CD are line segments /* from comp.graphics.algo Solving the above for r and s yields (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy) r = ----------------------------- (eqn 1) (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) s = ----------------------------- (eqn 2) (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) Let P be the position vector of the intersection point, then P=A+r(B-A) or Px=Ax+r(Bx-Ax) Py=Ay+r(By-Ay) By examining the values of r & s, you can also determine some other limiting conditions: If 0<=r<=1 & 0<=s<=1, intersection exists r<0 or r>1 or s<0 or s>1 line segments do not intersect If the denominator in eqn 1 is zero, AB & CD are parallel If the numerator in eqn 1 is also zero, AB & CD are collinear. */ var r_top, r_bot, s_top, s_bot, s, r; r_top = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y); r_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x); s_top = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y); s_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x); if ((r_bot === 0) || (s_bot === 0)) { return Math.min(jsts.algorithm.CGAlgorithms.distancePointLine(A, C, D), Math.min(jsts.algorithm.CGAlgorithms.distancePointLine(B, C, D), Math .min(jsts.algorithm.CGAlgorithms.distancePointLine(C, A, B), jsts.algorithm.CGAlgorithms.distancePointLine(D, A, B)))); } s = s_top / s_bot; r = r_top / r_bot; if ((r < 0) || (r > 1) || (s < 0) || (s > 1)) { // no intersection return Math.min(jsts.algorithm.CGAlgorithms.distancePointLine(A, C, D), Math.min(jsts.algorithm.CGAlgorithms.distancePointLine(B, C, D), Math .min(jsts.algorithm.CGAlgorithms.distancePointLine(C, A, B), jsts.algorithm.CGAlgorithms.distancePointLine(D, A, B)))); } return 0.0; // intersection exists }; /** * Computes the signed area for a ring. The signed area is positive if the ring * is oriented CW, negative if the ring is oriented CCW, and zero if the ring is * degenerate or flat. * * @param {Array{jsts.geom.Coordinate}} * ring the coordinates forming the ring * @return {Number} the signed area of the ring. */ jsts.algorithm.CGAlgorithms.signedArea = function(ring) { if (ring.length < 3) { return 0.0; } var sum, i, il, bx, by, cx, cy; sum = 0.0; for (i = 0, il = ring.length - 1; i < il; i++) { bx = ring[i].x; by = ring[i].y; cx = ring[i + 1].x; cy = ring[i + 1].y; sum += (bx + cx) * (cy - by); } return -sum / 2.0; }; /** * Computes the signed area for a ring. The signed area is: * <ul> * <li>positive if the ring is oriented CW * <li>negative if the ring is oriented CCW * <li>zero if the ring is degenerate or flat * </ul> * * @param {Array{jsts.geom.Coordinate}} * ring the coordinates forming the ring * @return {Number} the signed area of the ring. */ jsts.algorithm.CGAlgorithms.signedArea = function(ring) { var n, sum, p, bx, by, i, cx, cy; n = ring.length; if (n < 3) { return 0.0; } sum = 0.0; p = ring[0]; bx = p.x; by = p.y; for (i = 1; i < n; i++) { p = ring[i]; cx = p.x; cy = p.y; sum += (bx + cx) * (cy - by); bx = cx; by = cy; } return -sum / 2.0; }; /** * Computes the length of a linestring specified by a sequence of points. * * NOTE: This is renamed from length() to computeLength() because 'length' is a * reserved keyword in javascript. * * @param {Array{jsts.geom.Coordinate}} * pts the points specifying the linestring * @return {Number} the length of the linestring. */ jsts.algorithm.CGAlgorithms.computeLength = function(pts) { // optimized for processing CoordinateSequences var n = pts.length, len, x0, y0, x1, y1, dx, dy, p, i, il; if (n <= 1) { return 0.0; } len = 0.0; p = pts[0]; x0 = p.x; y0 = p.y; i = 1, il = n; for (i; i < n; i++) { p = pts[i]; x1 = p.x; y1 = p.y; dx = x1 - x0; dy = y1 - y0; len += Math.sqrt(dx * dx + dy * dy); x0 = x1; y0 = y1; } return len; }; /** * @see {jsts.algorithm.CGAlgorithms.computeLength} Since 'length' is a reserved * keyword in javascript this function does not act as a function. Please * use 'computeLength' instead. */ jsts.algorithm.CGAlgorithms.length = function() {}; /* ====================================================================== jsts/algorithm/Angle.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** * Utility functions for working with angles. * Unless otherwise noted, methods in this class express angles in radians. * * @requires jsts/algorithm/CGAlgorithms.js * * @constructor */ jsts.algorithm.Angle = function() { }; /** * Pi*2 */ jsts.algorithm.Angle.PI_TIMES_2 = 2.0 * Math.PI; /** * Pi/2 */ jsts.algorithm.Angle.PI_OVER_2 = Math.PI / 2.0; /** * Pi/4 */ jsts.algorithm.Angle.PI_OVER_4 = Math.PI / 4.0; /** * Constant representing counterclockwise orientation */ jsts.algorithm.Angle.COUNTERCLOCKWISE = jsts.algorithm.CGAlgorithms.COUNTERCLOCKWISE; /** * Constant representing clockwise orientation */ jsts.algorithm.Angle.CLOCKWISE = jsts.algorithm.CGAlgorithms.CLOCKWISE; /** * Constant representing no orientation */ jsts.algorithm.Angle.NONE = jsts.algorithm.CGAlgorithms.COLLINEAR; /** * Converts from radians to degrees. * * @param {Number} * radians an angle in radians. * @return {Number} * the angle in degrees. */ jsts.algorithm.Angle.toDegrees = function(radians) { return (radians * 180) / Math.PI; }; /** * Converts from degrees to radians. * * @param {Number} * angleDegrees an angle in degrees. * @return {Number} * the angle in radians. */ jsts.algorithm.Angle.toRadians = function(angleDegrees) { return (angleDegrees * Math.PI) / 180.0; }; /** * Returns the angle * Calls correct angle* depending on argument * * @return {Number} * The angle in radians. */ jsts.algorithm.Angle.angle = function() { if (arguments.length === 1) { return jsts.algorithm.Angle.angleFromOrigo(arguments[0]); }else { return jsts.algorithm.Angle.angleBetweenCoords(arguments[0], arguments[1]); } }; /** * Returns the angle of the vector from p0 to p1, * relative to the positive X-axis. * The angle is normalized to be in the range [ -Pi, Pi ]. * * @param {jsts.geom.Coordinate} * p0 a coordinate. * @param {jsts.geom.Coordinate} * p1 a coordinate. * @return {Number} * the normalized angle (in radians) that p0-p1 makes with the positive * x-axis. */ jsts.algorithm.Angle.angleBetweenCoords = function(p0, p1) { var dx, dy; dx = p1.x - p0.x; dy = p1.y - p0.y; return Math.atan2(dy, dx); }; /** * Returns the angle that the vector from (0,0) to p, * relative to the positive X-axis. * The angle is normalized to be in the range ( -Pi, Pi ]. * * @param {jsts.geom.Coordinate} * p a coordinate. * @return {Number} * the normalized angle (in radians) that p makes with the positive * x-axis. */ jsts.algorithm.Angle.angleFromOrigo = function(p) { return Math.atan2(p.y, p.x); }; /** * Tests whether the angle between p0-p1-p2 is acute. * An angle is acute if it is less than 90 degrees. * <p> * Note: this implementation is not precise (determistic) for angles very close to 90 degrees. * * @param {jsts.geom.Coordinate} * p0 an endpoint of the angle. * @param {jsts.geom.Coordinate} * p1 the base of the angle. * @param {jsts.geom.Coordinate} * p2 the other endpoint of the angle. * @return {Boolean} * true if the angle is acute. */ jsts.algorithm.Angle.isAcute = function(p0, p1, p2) { var dx0, dy0, dx1, dy1, dotprod; //relies on fact that A dot B is positive if A ang B is acute dx0 = p0.x - p1.x; dy0 = p0.y - p1.y; dx1 = p2.x - p1.x; dy1 = p2.y - p1.y; dotprod = dx0 * dx1 + dy0 * dy1; return dotprod > 0; }; /** * Tests whether the angle between p0-p1-p2 is obtuse. * An angle is obtuse if it is greater than 90 degrees. * <p> * Note: this implementation is not precise (determistic) for angles very close to 90 degrees. * * @param {jsts.geom.Coordinate} * p0 an endpoint of the angle. * @param {jsts.geom.Coordinate} * p1 the base of the angle. * @param {jsts.geom.Coordinate} * p2 the other endpoint of the angle. * @return {Boolean} * true if the angle is obtuse. */ jsts.algorithm.Angle.isObtuse = function(p0, p1, p2) { var dx0, dy0, dx1, dy1, dotprod; //relies on fact that A dot B is negative iff A ang B is obtuse dx0 = p0.x - p1.x; dy0 = p0.y - p1.y; dx1 = p2.x - p1.x; dy1 = p2.y - p1.y; dotprod = dx0 * dx1 + dy0 * dy1; return dotprod < 0; }; /** * Returns the unoriented smallest angle between two vectors. * The computed angle will be in the range [0, Pi). * * @param {jsts.geom.Coordinate} * tip1 the tip of one vector. * @param {jsts.geom.Coordinate} * tail the tail of each vector. * @param {jsts.geom.Coordinate} * tip2 the tip of the other vector. * @return {Number} * the angle between tail-tip1 and tail-tip2. */ jsts.algorithm.Angle.angleBetween = function(tip1, tail, tip2) { var a1, a2; a1 = jsts.algorithm.Angle.angle(tail, tip1); a2 = jsts.algorithm.Angle.angle(tail, tip2); return jsts.algorithm.Angle.diff(a1, a2); }; /** * Returns the oriented smallest angle between two vectors. * The computed angle will be in the range (-Pi, Pi]. * A positive result corresponds to a counterclockwise rotation * from v1 to v2; * a negative result corresponds to a clockwise rotation. * * @param {jsts.geom.Coordinate} * tip1 the tip of v1. * @param {jsts.geom.Coordinate} * tail the tail of each vector. * @param {jsts.geom.Coordinate} * tip2 the tip of v2. * @return {Number} * the angle between v1 and v2, relative to v1. */ jsts.algorithm.Angle.angleBetweenOriented = function(tip1, tail, tip2) { var a1, a2, angDel; a1 = jsts.algorithm.Angle.angle(tail, tip1); a2 = jsts.algorithm.Angle.angle(tail, tip2); angDel = a2 - a1; // normalize, maintaining orientation if (angDel <= -Math.PI) { return angDel + jsts.algorithm.Angle.PI_TIMES_2; } if (angDel > Math.PI) { return angDel - jsts.algorithm.Angle.PI_TIMES_2; } return angDel; }; /** * Computes the interior angle between two segments of a ring. The ring is * assumed to be oriented in a clockwise direction. The computed angle will be * in the range [0, 2Pi] * * @param {jsts.geom.Coordinate} * p0 a point of the ring. * @param {jsts.geom.Coordinate} * p1 the next point of the ring. * @param {jsts.geom.Coordinate} * p2 the next point of the ring. * @return {Number} * the interior angle based at <code>p1.</code> */ jsts.algorithm.Angle.interiorAngle = function(p0, p1, p2) { var anglePrev, angleNext; anglePrev = jsts.algorithm.Angle.angle(p1, p0); angleNext = jsts.algorithm.Angle.angle(p1, p2); return Math.abs(angleNext - anglePrev); }; /** * Returns whether an angle must turn clockwise or counterclockwise * to overlap another angle. * * @param {Number} * ang1 an angle (in radians). * @param {Number} * ang2 an angle (in radians). * @return {Number} * whether a1 must turn CLOCKWISE, COUNTERCLOCKWISE or NONE to * overlap a2. */ jsts.algorithm.Angle.getTurn = function(ang1, ang2) { var crossproduct = Math.sin(ang2 - ang1); if (crossproduct > 0) { return jsts.algorithm.Angle.COUNTERCLOCKWISE; } if (crossproduct < 0) { return jsts.algorithm.Angle.CLOCKWISE; } return jsts.algorithm.Angle.NONE; }; /** * Computes the normalized value of an angle, which is the * equivalent angle in the range ( -Pi, Pi ]. * * @param {Number} * angle the angle to normalize. * @return {Number} * an equivalent angle in the range (-Pi, Pi]. */ jsts.algorithm.Angle.normalize = function(angle) { while (angle > Math.PI) { angle -= jsts.algorithm.Angle.PI_TIMES_2; } while (angle <= -Math.PI) { angle += jsts.algorithm.Angle.PI_TIMES_2; } return angle; }; /** * Computes the normalized positive value of an angle, which is the * equivalent angle in the range [ 0, 2*Pi ). * E.g.: * <ul> * <li>normalizePositive(0.0) = 0.0 * <li>normalizePositive(-PI) = PI * <li>normalizePositive(-2PI) = 0.0 * <li>normalizePositive(-3PI) = PI * <li>normalizePositive(-4PI) = 0 * <li>normalizePositive(PI) = PI * <li>normalizePositive(2PI) = 0.0 * <li>normalizePositive(3PI) = PI * <li>normalizePositive(4PI) = 0.0 * </ul> * * @param {Number} * angle the angle to normalize, in radians. * @return {Number} * an equivalent positive angle. */ jsts.algorithm.Angle.normalizePositive = function(angle) { if (angle < 0.0) { while (angle < 0.0) { angle += jsts.algorithm.Angle.PI_TIMES_2; } // in case round-off error bumps the value over if (angle >= jsts.algorithm.Angle.PI_TIMES_2) { angle = 0.0; } } else { while (angle >= jsts.algorithm.Angle.PI_TIMES_2) { angle -= jsts.algorithm.Angle.PI_TIMES_2; } // in case round-off error bumps the value under if (angle < 0.0) { angle = 0.0; } } return angle; }; /** * Computes the unoriented smallest difference between two angles. * The angles are assumed to be normalized to the range [-Pi, Pi]. * The result will be in the range [0, Pi]. * * @param {Number} * ang1 the angle of one vector (in [-Pi, Pi] ). * @param {Number} * ang2 the angle of the other vector (in range [-Pi, Pi] ). * @return {Number} * the angle (in radians) between the two vectors (in range [0, Pi] ). */ jsts.algorithm.Angle.diff = function(ang1, ang2) { var delAngle; if (ang1 < ang2) { delAngle = ang2 - ang1; } else { delAngle = ang1 - ang2; } if (delAngle > Math.PI) { delAngle = (2 * Math.PI) - delAngle; } return delAngle; }; /* ====================================================================== jsts/geom/GeometryComponentFilter.js ====================================================================== */ /* Copyright (c) 2011 by The Authors. * Published under the LGPL 2.1 license. * See /license-notice.txt for the full text of the license notice. * See /license.txt for the full text of the license. */ /** * <code>Geometry</code> classes support the concept of applying a * <code>GeometryComponentFilter</code> filter to the <code>Geometry</code>. * The filter is applied to every component of the <code>Geometry</code> which * is itself a <code>Geometry</code> and which does not itself contain any * components. (For instance, all the {@link LinearRing}s in {@link Polygon}s * are visited, but in a {@link MultiPolygon} the {@link Polygon}s themselves * are not visited.) Thus the only classes of Geometry which must be handled as * arguments to {@link #filter} are {@link LineString}s, {@link LinearRing}s * and {@link Point}s. * <p> * A <code>GeometryComponentFilter</code> filter can either record information * about the <code>Geometry</code> or change the <code>Geometry