terraformer.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420
  1. (function (root, factory) {
  2. // Node.
  3. if(typeof module === 'object' && typeof module.exports === 'object') {
  4. exports = module.exports = factory();
  5. }
  6. // Browser Global.
  7. if(typeof window === "object") {
  8. root.Terraformer = factory();
  9. }
  10. }(this, function(){
  11. "use strict";
  12. var exports = {},
  13. EarthRadius = 6378137,
  14. DegreesPerRadian = 57.295779513082320,
  15. RadiansPerDegree = 0.017453292519943,
  16. MercatorCRS = {
  17. "type": "link",
  18. "properties": {
  19. "href": "http://spatialreference.org/ref/sr-org/6928/ogcwkt/",
  20. "type": "ogcwkt"
  21. }
  22. },
  23. GeographicCRS = {
  24. "type": "link",
  25. "properties": {
  26. "href": "http://spatialreference.org/ref/epsg/4326/ogcwkt/",
  27. "type": "ogcwkt"
  28. }
  29. };
  30. /*
  31. Internal: isArray function
  32. */
  33. function isArray(obj) {
  34. return Object.prototype.toString.call(obj) === "[object Array]";
  35. }
  36. /*
  37. Internal: safe warning
  38. */
  39. function warn() {
  40. var args = Array.prototype.slice.apply(arguments);
  41. if (typeof console !== undefined && console.warn) {
  42. console.warn.apply(console, args);
  43. }
  44. }
  45. /*
  46. Internal: Extend one object with another.
  47. */
  48. function extend(destination, source) {
  49. for (var k in source) {
  50. if (source.hasOwnProperty(k)) {
  51. destination[k] = source[k];
  52. }
  53. }
  54. return destination;
  55. }
  56. /*
  57. Public: Calculate an bounding box for a geojson object
  58. */
  59. function calculateBounds (geojson) {
  60. if(geojson.type){
  61. switch (geojson.type) {
  62. case 'Point':
  63. return [ geojson.coordinates[0], geojson.coordinates[1], geojson.coordinates[0], geojson.coordinates[1]];
  64. case 'MultiPoint':
  65. return calculateBoundsFromArray(geojson.coordinates);
  66. case 'LineString':
  67. return calculateBoundsFromArray(geojson.coordinates);
  68. case 'MultiLineString':
  69. return calculateBoundsFromNestedArrays(geojson.coordinates);
  70. case 'Polygon':
  71. return calculateBoundsFromNestedArrays(geojson.coordinates);
  72. case 'MultiPolygon':
  73. return calculateBoundsFromNestedArrayOfArrays(geojson.coordinates);
  74. case 'Feature':
  75. return geojson.geometry? calculateBounds(geojson.geometry) : null;
  76. case 'FeatureCollection':
  77. return calculateBoundsForFeatureCollection(geojson);
  78. case 'GeometryCollection':
  79. return calculateBoundsForGeometryCollection(geojson);
  80. default:
  81. throw new Error("Unknown type: " + geojson.type);
  82. }
  83. }
  84. return null;
  85. }
  86. /*
  87. Internal: Calculate an bounding box from an nested array of positions
  88. [
  89. [
  90. [ [lng, lat],[lng, lat],[lng, lat] ]
  91. ]
  92. [
  93. [lng, lat],[lng, lat],[lng, lat]
  94. ]
  95. [
  96. [lng, lat],[lng, lat],[lng, lat]
  97. ]
  98. ]
  99. */
  100. function calculateBoundsFromNestedArrays (array) {
  101. var x1 = null, x2 = null, y1 = null, y2 = null;
  102. for (var i = 0; i < array.length; i++) {
  103. var inner = array[i];
  104. for (var j = 0; j < inner.length; j++) {
  105. var lonlat = inner[j];
  106. var lon = lonlat[0];
  107. var lat = lonlat[1];
  108. if (x1 === null) {
  109. x1 = lon;
  110. } else if (lon < x1) {
  111. x1 = lon;
  112. }
  113. if (x2 === null) {
  114. x2 = lon;
  115. } else if (lon > x2) {
  116. x2 = lon;
  117. }
  118. if (y1 === null) {
  119. y1 = lat;
  120. } else if (lat < y1) {
  121. y1 = lat;
  122. }
  123. if (y2 === null) {
  124. y2 = lat;
  125. } else if (lat > y2) {
  126. y2 = lat;
  127. }
  128. }
  129. }
  130. return [x1, y1, x2, y2 ];
  131. }
  132. /*
  133. Internal: Calculate a bounding box from an array of arrays of arrays
  134. [
  135. [ [lng, lat],[lng, lat],[lng, lat] ]
  136. [ [lng, lat],[lng, lat],[lng, lat] ]
  137. [ [lng, lat],[lng, lat],[lng, lat] ]
  138. ]
  139. */
  140. function calculateBoundsFromNestedArrayOfArrays (array) {
  141. var x1 = null, x2 = null, y1 = null, y2 = null;
  142. for (var i = 0; i < array.length; i++) {
  143. var inner = array[i];
  144. for (var j = 0; j < inner.length; j++) {
  145. var innerinner = inner[j];
  146. for (var k = 0; k < innerinner.length; k++) {
  147. var lonlat = innerinner[k];
  148. var lon = lonlat[0];
  149. var lat = lonlat[1];
  150. if (x1 === null) {
  151. x1 = lon;
  152. } else if (lon < x1) {
  153. x1 = lon;
  154. }
  155. if (x2 === null) {
  156. x2 = lon;
  157. } else if (lon > x2) {
  158. x2 = lon;
  159. }
  160. if (y1 === null) {
  161. y1 = lat;
  162. } else if (lat < y1) {
  163. y1 = lat;
  164. }
  165. if (y2 === null) {
  166. y2 = lat;
  167. } else if (lat > y2) {
  168. y2 = lat;
  169. }
  170. }
  171. }
  172. }
  173. return [x1, y1, x2, y2];
  174. }
  175. /*
  176. Internal: Calculate a bounding box from an array of positions
  177. [
  178. [lng, lat],[lng, lat],[lng, lat]
  179. ]
  180. */
  181. function calculateBoundsFromArray (array) {
  182. var x1 = null, x2 = null, y1 = null, y2 = null;
  183. for (var i = 0; i < array.length; i++) {
  184. var lonlat = array[i];
  185. var lon = lonlat[0];
  186. var lat = lonlat[1];
  187. if (x1 === null) {
  188. x1 = lon;
  189. } else if (lon < x1) {
  190. x1 = lon;
  191. }
  192. if (x2 === null) {
  193. x2 = lon;
  194. } else if (lon > x2) {
  195. x2 = lon;
  196. }
  197. if (y1 === null) {
  198. y1 = lat;
  199. } else if (lat < y1) {
  200. y1 = lat;
  201. }
  202. if (y2 === null) {
  203. y2 = lat;
  204. } else if (lat > y2) {
  205. y2 = lat;
  206. }
  207. }
  208. return [x1, y1, x2, y2 ];
  209. }
  210. /*
  211. Internal: Calculate an bounding box for a feature collection
  212. */
  213. function calculateBoundsForFeatureCollection(featureCollection){
  214. var extents = [], extent;
  215. for (var i = featureCollection.features.length - 1; i >= 0; i--) {
  216. extent = calculateBounds(featureCollection.features[i].geometry);
  217. extents.push([extent[0],extent[1]]);
  218. extents.push([extent[2],extent[3]]);
  219. }
  220. return calculateBoundsFromArray(extents);
  221. }
  222. /*
  223. Internal: Calculate an bounding box for a geometry collection
  224. */
  225. function calculateBoundsForGeometryCollection(geometryCollection){
  226. var extents = [], extent;
  227. for (var i = geometryCollection.geometries.length - 1; i >= 0; i--) {
  228. extent = calculateBounds(geometryCollection.geometries[i]);
  229. extents.push([extent[0],extent[1]]);
  230. extents.push([extent[2],extent[3]]);
  231. }
  232. return calculateBoundsFromArray(extents);
  233. }
  234. function calculateEnvelope(geojson){
  235. var bounds = calculateBounds(geojson);
  236. return {
  237. x: bounds[0],
  238. y: bounds[1],
  239. w: Math.abs(bounds[0] - bounds[2]),
  240. h: Math.abs(bounds[1] - bounds[3])
  241. };
  242. }
  243. /*
  244. Internal: Convert radians to degrees. Used by spatial reference converters.
  245. */
  246. function radToDeg(rad) {
  247. return rad * DegreesPerRadian;
  248. }
  249. /*
  250. Internal: Convert degrees to radians. Used by spatial reference converters.
  251. */
  252. function degToRad(deg) {
  253. return deg * RadiansPerDegree;
  254. }
  255. /*
  256. Internal: Loop over each array in a geojson object and apply a function to it. Used by spatial reference converters.
  257. */
  258. function eachPosition(coordinates, func) {
  259. for (var i = 0; i < coordinates.length; i++) {
  260. // we found a number so lets convert this pair
  261. if(typeof coordinates[i][0] === "number"){
  262. coordinates[i] = func(coordinates[i]);
  263. }
  264. // we found an coordinates array it again and run THIS function against it
  265. if(typeof coordinates[i] === "object"){
  266. coordinates[i] = eachPosition(coordinates[i], func);
  267. }
  268. }
  269. return coordinates;
  270. }
  271. /*
  272. Public: Convert a GeoJSON Position object to Geographic (4326)
  273. */
  274. function positionToGeographic(position) {
  275. var x = position[0];
  276. var y = position[1];
  277. return [radToDeg(x / EarthRadius) - (Math.floor((radToDeg(x / EarthRadius) + 180) / 360) * 360), radToDeg((Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * y / EarthRadius))))];
  278. }
  279. /*
  280. Public: Convert a GeoJSON Position object to Web Mercator (102100)
  281. */
  282. function positionToMercator(position) {
  283. var lng = position[0];
  284. var lat = Math.max(Math.min(position[1], 89.99999), -89.99999);
  285. return [degToRad(lng) * EarthRadius, EarthRadius/2.0 * Math.log( (1.0 + Math.sin(degToRad(lat))) / (1.0 - Math.sin(degToRad(lat))) )];
  286. }
  287. /*
  288. Public: Apply a function agaist all positions in a geojson object. Used by spatial reference converters.
  289. */
  290. function applyConverter(geojson, converter, noCrs){
  291. if(geojson.type === "Point") {
  292. geojson.coordinates = converter(geojson.coordinates);
  293. } else if(geojson.type === "Feature") {
  294. geojson.geometry = applyConverter(geojson.geometry, converter, true);
  295. } else if(geojson.type === "FeatureCollection") {
  296. for (var f = 0; f < geojson.features.length; f++) {
  297. geojson.features[f] = applyConverter(geojson.features[f], converter, true);
  298. }
  299. } else if(geojson.type === "GeometryCollection") {
  300. for (var g = 0; g < geojson.geometries.length; g++) {
  301. geojson.geometries[g] = applyConverter(geojson.geometries[g], converter, true);
  302. }
  303. } else {
  304. geojson.coordinates = eachPosition(geojson.coordinates, converter);
  305. }
  306. if(!noCrs){
  307. if(converter === positionToMercator){
  308. geojson.crs = MercatorCRS;
  309. }
  310. }
  311. if(converter === positionToGeographic){
  312. delete geojson.crs;
  313. }
  314. return geojson;
  315. }
  316. /*
  317. Public: Convert a GeoJSON object to ESRI Web Mercator (102100)
  318. */
  319. function toMercator(geojson) {
  320. return applyConverter(geojson, positionToMercator);
  321. }
  322. /*
  323. Convert a GeoJSON object to Geographic coordinates (WSG84, 4326)
  324. */
  325. function toGeographic(geojson) {
  326. return applyConverter(geojson, positionToGeographic);
  327. }
  328. /*
  329. Internal: -1,0,1 comparison function
  330. */
  331. function cmp(a, b) {
  332. if(a < b) {
  333. return -1;
  334. } else if(a > b) {
  335. return 1;
  336. } else {
  337. return 0;
  338. }
  339. }
  340. /*
  341. Internal: used for sorting
  342. */
  343. function compSort(p1, p2) {
  344. if (p1[0] > p2[0]) {
  345. return -1;
  346. } else if (p1[0] < p2[0]) {
  347. return 1;
  348. } else if (p1[1] > p2[1]) {
  349. return -1;
  350. } else if (p1[1] < p2[1]) {
  351. return 1;
  352. } else {
  353. return 0;
  354. }
  355. }
  356. /*
  357. Internal: used to determine turn
  358. */
  359. function turn(p, q, r) {
  360. // Returns -1, 0, 1 if p,q,r forms a right, straight, or left turn.
  361. return cmp((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1]), 0);
  362. }
  363. /*
  364. Internal: used to determine euclidean distance between two points
  365. */
  366. function euclideanDistance(p, q) {
  367. // Returns the squared Euclidean distance between p and q.
  368. var dx = q[0] - p[0];
  369. var dy = q[1] - p[1];
  370. return dx * dx + dy * dy;
  371. }
  372. function nextHullPoint(points, p) {
  373. // Returns the next point on the convex hull in CCW from p.
  374. var q = p;
  375. for(var r in points) {
  376. var t = turn(p, q, points[r]);
  377. if(t === -1 || t === 0 && euclideanDistance(p, points[r]) > euclideanDistance(p, q)) {
  378. q = points[r];
  379. }
  380. }
  381. return q;
  382. }
  383. function convexHull(points) {
  384. // implementation of the Jarvis March algorithm
  385. // adapted from http://tixxit.wordpress.com/2009/12/09/jarvis-march/
  386. if(points.length === 0) {
  387. return [];
  388. } else if(points.length === 1) {
  389. return points;
  390. }
  391. // Returns the points on the convex hull of points in CCW order.
  392. var hull = [points.sort(compSort)[0]];
  393. for(var p = 0; p < hull.length; p++) {
  394. var q = nextHullPoint(points, hull[p]);
  395. if(q !== hull[0]) {
  396. hull.push(q);
  397. }
  398. }
  399. return hull;
  400. }
  401. function isConvex(points) {
  402. var ltz;
  403. for (var i = 0; i < points.length - 3; i++) {
  404. var p1 = points[i];
  405. var p2 = points[i + 1];
  406. var p3 = points[i + 2];
  407. var v = [p2[0] - p1[0], p2[1] - p1[1]];
  408. // p3.x * v.y - p3.y * v.x + v.x * p1.y - v.y * p1.x
  409. var res = p3[0] * v[1] - p3[1] * v[0] + v[0] * p1[1] - v[1] * p1[0];
  410. if (i === 0) {
  411. if (res < 0) {
  412. ltz = true;
  413. } else {
  414. ltz = false;
  415. }
  416. } else {
  417. if (ltz && (res > 0) || !ltz && (res < 0)) {
  418. return false;
  419. }
  420. }
  421. }
  422. return true;
  423. }
  424. function coordinatesContainPoint(coordinates, point) {
  425. var contains = false;
  426. for(var i = -1, l = coordinates.length, j = l - 1; ++i < l; j = i) {
  427. if (((coordinates[i][1] <= point[1] && point[1] < coordinates[j][1]) ||
  428. (coordinates[j][1] <= point[1] && point[1] < coordinates[i][1])) &&
  429. (point[0] < (coordinates[j][0] - coordinates[i][0]) * (point[1] - coordinates[i][1]) / (coordinates[j][1] - coordinates[i][1]) + coordinates[i][0])) {
  430. contains = !contains;
  431. }
  432. }
  433. return contains;
  434. }
  435. function polygonContainsPoint(polygon, point) {
  436. if (polygon && polygon.length) {
  437. if (polygon.length === 1) { // polygon with no holes
  438. return coordinatesContainPoint(polygon[0], point);
  439. } else { // polygon with holes
  440. if (coordinatesContainPoint(polygon[0], point)) {
  441. for (var i = 1; i < polygon.length; i++) {
  442. if (coordinatesContainPoint(polygon[i], point)) {
  443. return false; // found in hole
  444. }
  445. }
  446. return true;
  447. } else {
  448. return false;
  449. }
  450. }
  451. } else {
  452. return false;
  453. }
  454. }
  455. function edgeIntersectsEdge(a1, a2, b1, b2) {
  456. var ua_t = (b2[0] - b1[0]) * (a1[1] - b1[1]) - (b2[1] - b1[1]) * (a1[0] - b1[0]);
  457. var ub_t = (a2[0] - a1[0]) * (a1[1] - b1[1]) - (a2[1] - a1[1]) * (a1[0] - b1[0]);
  458. var u_b = (b2[1] - b1[1]) * (a2[0] - a1[0]) - (b2[0] - b1[0]) * (a2[1] - a1[1]);
  459. if ( u_b !== 0 ) {
  460. var ua = ua_t / u_b;
  461. var ub = ub_t / u_b;
  462. if ( 0 <= ua && ua <= 1 && 0 <= ub && ub <= 1 ) {
  463. return true;
  464. }
  465. }
  466. return false;
  467. }
  468. function isNumber(n) {
  469. return !isNaN(parseFloat(n)) && isFinite(n);
  470. }
  471. function arraysIntersectArrays(a, b) {
  472. if (isNumber(a[0][0])) {
  473. if (isNumber(b[0][0])) {
  474. for (var i = 0; i < a.length - 1; i++) {
  475. for (var j = 0; j < b.length - 1; j++) {
  476. if (edgeIntersectsEdge(a[i], a[i + 1], b[j], b[j + 1])) {
  477. return true;
  478. }
  479. }
  480. }
  481. } else {
  482. for (var k = 0; k < b.length; k++) {
  483. if (arraysIntersectArrays(a, b[k])) {
  484. return true;
  485. }
  486. }
  487. }
  488. } else {
  489. for (var l = 0; l < a.length; l++) {
  490. if (arraysIntersectArrays(a[l], b)) {
  491. return true;
  492. }
  493. }
  494. }
  495. return false;
  496. }
  497. /*
  498. Internal: Returns a copy of coordinates for s closed polygon
  499. */
  500. function closedPolygon(coordinates) {
  501. var outer = [ ];
  502. for (var i = 0; i < coordinates.length; i++) {
  503. var inner = coordinates[i].slice();
  504. if (pointsEqual(inner[0], inner[inner.length - 1]) === false) {
  505. inner.push(inner[0]);
  506. }
  507. outer.push(inner);
  508. }
  509. return outer;
  510. }
  511. function pointsEqual(a, b) {
  512. for (var i = 0; i < a.length; i++) {
  513. if (a[i] !== b[i]) {
  514. return false;
  515. }
  516. }
  517. return true;
  518. }
  519. function coordinatesEqual(a, b) {
  520. if (a.length !== b.length) {
  521. return false;
  522. }
  523. var na = a.slice().sort(compSort);
  524. var nb = b.slice().sort(compSort);
  525. for (var i = 0; i < na.length; i++) {
  526. if (na[i].length !== nb[i].length) {
  527. return false;
  528. }
  529. for (var j = 0; j < na.length; j++) {
  530. if (na[i][j] !== nb[i][j]) {
  531. return false;
  532. }
  533. }
  534. }
  535. return true;
  536. }
  537. /*
  538. Internal: An array of variables that will be excluded form JSON objects.
  539. */
  540. var excludeFromJSON = ["length"];
  541. /*
  542. Internal: Base GeoJSON Primitive
  543. */
  544. function Primitive(geojson){
  545. if(geojson){
  546. switch (geojson.type) {
  547. case 'Point':
  548. return new Point(geojson);
  549. case 'MultiPoint':
  550. return new MultiPoint(geojson);
  551. case 'LineString':
  552. return new LineString(geojson);
  553. case 'MultiLineString':
  554. return new MultiLineString(geojson);
  555. case 'Polygon':
  556. return new Polygon(geojson);
  557. case 'MultiPolygon':
  558. return new MultiPolygon(geojson);
  559. case 'Feature':
  560. return new Feature(geojson);
  561. case 'FeatureCollection':
  562. return new FeatureCollection(geojson);
  563. case 'GeometryCollection':
  564. return new GeometryCollection(geojson);
  565. default:
  566. throw new Error("Unknown type: " + geojson.type);
  567. }
  568. }
  569. }
  570. Primitive.prototype.toMercator = function(){
  571. return toMercator(this);
  572. };
  573. Primitive.prototype.toGeographic = function(){
  574. return toGeographic(this);
  575. };
  576. Primitive.prototype.envelope = function(){
  577. return calculateEnvelope(this);
  578. };
  579. Primitive.prototype.bbox = function(){
  580. return calculateBounds(this);
  581. };
  582. Primitive.prototype.convexHull = function(){
  583. var coordinates = [ ], i, j;
  584. if (this.type === 'Point') {
  585. return null;
  586. } else if (this.type === 'LineString' || this.type === 'MultiPoint') {
  587. if (this.coordinates && this.coordinates.length >= 3) {
  588. coordinates = this.coordinates;
  589. } else {
  590. return null;
  591. }
  592. } else if (this.type === 'Polygon' || this.type === 'MultiLineString') {
  593. if (this.coordinates && this.coordinates.length > 0) {
  594. for (i = 0; i < this.coordinates.length; i++) {
  595. coordinates = coordinates.concat(this.coordinates[i]);
  596. }
  597. if(coordinates.length < 3){
  598. return null;
  599. }
  600. } else {
  601. return null;
  602. }
  603. } else if (this.type === 'MultiPolygon') {
  604. if (this.coordinates && this.coordinates.length > 0) {
  605. for (i = 0; i < this.coordinates.length; i++) {
  606. for (j = 0; j < this.coordinates[i].length; j++) {
  607. coordinates = coordinates.concat(this.coordinates[i][j]);
  608. }
  609. }
  610. if(coordinates.length < 3){
  611. return null;
  612. }
  613. } else {
  614. return null;
  615. }
  616. } else if(this.type === "Feature"){
  617. var primitive = new Primitive(this.geometry);
  618. return primitive.convexHull();
  619. }
  620. return new Polygon({
  621. type: 'Polygon',
  622. coordinates: closedPolygon([convexHull(coordinates)])
  623. });
  624. };
  625. Primitive.prototype.toJSON = function(){
  626. var obj = {};
  627. for (var key in this) {
  628. if (this.hasOwnProperty(key) && excludeFromJSON.indexOf(key) === -1) {
  629. obj[key] = this[key];
  630. }
  631. }
  632. obj.bbox = calculateBounds(this);
  633. return obj;
  634. };
  635. Primitive.prototype.contains = function(primitive){
  636. return new Primitive(primitive).within(this);
  637. };
  638. Primitive.prototype.within = function(primitive) {
  639. var coordinates, i, j, contains;
  640. // if we are passed a feature, use the polygon inside instead
  641. if (primitive.type === 'Feature') {
  642. primitive = primitive.geometry;
  643. }
  644. // point.within(point) :: equality
  645. if (primitive.type === "Point") {
  646. if (this.type === "Point") {
  647. return pointsEqual(this.coordinates, primitive.coordinates);
  648. }
  649. }
  650. // point.within(multilinestring)
  651. if (primitive.type === "MultiLineString") {
  652. if (this.type === "Point") {
  653. for (i = 0; i < primitive.coordinates.length; i++) {
  654. var linestring = { type: "LineString", coordinates: primitive.coordinates[i] };
  655. if (this.within(linestring)) {
  656. return true;
  657. }
  658. }
  659. }
  660. }
  661. // point.within(linestring), point.within(multipoint)
  662. if (primitive.type === "LineString" || primitive.type === "MultiPoint") {
  663. if (this.type === "Point") {
  664. for (i = 0; i < primitive.coordinates.length; i++) {
  665. if (this.coordinates.length !== primitive.coordinates[i].length) {
  666. return false;
  667. }
  668. if (pointsEqual(this.coordinates, primitive.coordinates[i])) {
  669. return true;
  670. }
  671. }
  672. }
  673. }
  674. if (primitive.type === "Polygon") {
  675. // polygon.within(polygon)
  676. if (this.type === "Polygon") {
  677. // check for equal polygons
  678. if (primitive.coordinates.length === this.coordinates.length) {
  679. for (i = 0; i < this.coordinates.length; i++) {
  680. if (coordinatesEqual(this.coordinates[i], primitive.coordinates[i])) {
  681. return true;
  682. }
  683. }
  684. }
  685. if (this.coordinates.length && polygonContainsPoint(primitive.coordinates, this.coordinates[0][0])) {
  686. return !arraysIntersectArrays(closedPolygon(this.coordinates), closedPolygon(primitive.coordinates));
  687. } else {
  688. return false;
  689. }
  690. // point.within(polygon)
  691. } else if (this.type === "Point") {
  692. return polygonContainsPoint(primitive.coordinates, this.coordinates);
  693. // linestring/multipoint withing polygon
  694. } else if (this.type === "LineString" || this.type === "MultiPoint") {
  695. if (!this.coordinates || this.coordinates.length === 0) {
  696. return false;
  697. }
  698. for (i = 0; i < this.coordinates.length; i++) {
  699. if (polygonContainsPoint(primitive.coordinates, this.coordinates[i]) === false) {
  700. return false;
  701. }
  702. }
  703. return true;
  704. // multilinestring.within(polygon)
  705. } else if (this.type === "MultiLineString") {
  706. for (i = 0; i < this.coordinates.length; i++) {
  707. var ls = new LineString(this.coordinates[i]);
  708. if (ls.within(primitive) === false) {
  709. contains++;
  710. return false;
  711. }
  712. }
  713. return true;
  714. // multipolygon.within(polygon)
  715. } else if (this.type === "MultiPolygon") {
  716. for (i = 0; i < this.coordinates.length; i++) {
  717. var p1 = new Primitive({ type: "Polygon", coordinates: this.coordinates[i] });
  718. if (p1.within(primitive) === false) {
  719. return false;
  720. }
  721. }
  722. return true;
  723. }
  724. }
  725. if (primitive.type === "MultiPolygon") {
  726. // point.within(multipolygon)
  727. if (this.type === "Point") {
  728. if (primitive.coordinates.length) {
  729. for (i = 0; i < primitive.coordinates.length; i++) {
  730. coordinates = primitive.coordinates[i];
  731. if (polygonContainsPoint(coordinates, this.coordinates) && arraysIntersectArrays([this.coordinates], primitive.coordinates) === false) {
  732. return true;
  733. }
  734. }
  735. }
  736. return false;
  737. // polygon.within(multipolygon)
  738. } else if (this.type === "Polygon") {
  739. for (i = 0; i < this.coordinates.length; i++) {
  740. if (primitive.coordinates[i].length === this.coordinates.length) {
  741. for (j = 0; j < this.coordinates.length; j++) {
  742. if (coordinatesEqual(this.coordinates[j], primitive.coordinates[i][j])) {
  743. return true;
  744. }
  745. }
  746. }
  747. }
  748. if (arraysIntersectArrays(this.coordinates, primitive.coordinates) === false) {
  749. if (primitive.coordinates.length) {
  750. for (i = 0; i < primitive.coordinates.length; i++) {
  751. coordinates = primitive.coordinates[i];
  752. if (polygonContainsPoint(coordinates, this.coordinates[0][0]) === false) {
  753. contains = false;
  754. } else {
  755. contains = true;
  756. }
  757. }
  758. return contains;
  759. }
  760. }
  761. // linestring.within(multipolygon), multipoint.within(multipolygon)
  762. } else if (this.type === "LineString" || this.type === "MultiPoint") {
  763. for (i = 0; i < primitive.coordinates.length; i++) {
  764. var p = { type: "Polygon", coordinates: primitive.coordinates[i] };
  765. if (this.within(p)) {
  766. return true;
  767. }
  768. return false;
  769. }
  770. // multilinestring.within(multipolygon)
  771. } else if (this.type === "MultiLineString") {
  772. for (i = 0; i < this.coordinates.length; i++) {
  773. var lines = new LineString(this.coordinates[i]);
  774. if (lines.within(primitive) === false) {
  775. return false;
  776. }
  777. }
  778. return true;
  779. // multipolygon.within(multipolygon)
  780. } else if (this.type === "MultiPolygon") {
  781. for (i = 0; i < primitive.coordinates.length; i++) {
  782. var mpoly = { type: "Polygon", coordinates: primitive.coordinates[i] };
  783. if (this.within(mpoly) === false) {
  784. return false;
  785. }
  786. }
  787. return true;
  788. }
  789. }
  790. // default to false
  791. return false;
  792. };
  793. Primitive.prototype.intersects = function(primitive) {
  794. // if we are passed a feature, use the polygon inside instead
  795. if (primitive.type === 'Feature') {
  796. primitive = primitive.geometry;
  797. }
  798. var p = new Primitive(primitive);
  799. if (this.within(primitive) || p.within(this)) {
  800. return true;
  801. }
  802. if (this.type !== 'Point' && this.type !== 'MultiPoint' &&
  803. primitive.type !== 'Point' && primitive.type !== 'MultiPoint') {
  804. return arraysIntersectArrays(this.coordinates, primitive.coordinates);
  805. } else if (this.type === 'Feature') {
  806. // in the case of a Feature, use the internal primitive for intersection
  807. var inner = new Primitive(this.geometry);
  808. return inner.intersects(primitive);
  809. }
  810. warn("Type " + this.type + " to " + primitive.type + " intersection is not supported by intersects");
  811. return false;
  812. };
  813. /*
  814. GeoJSON Point Class
  815. new Point();
  816. new Point(x,y,z,wtf);
  817. new Point([x,y,z,wtf]);
  818. new Point([x,y]);
  819. new Point({
  820. type: "Point",
  821. coordinates: [x,y]
  822. });
  823. */
  824. function Point(input){
  825. var args = Array.prototype.slice.call(arguments);
  826. if(input && input.type === "Point" && input.coordinates){
  827. extend(this, input);
  828. } else if(input && isArray(input)) {
  829. this.coordinates = input;
  830. } else if(args.length >= 2) {
  831. this.coordinates = args;
  832. } else {
  833. throw "Terraformer: invalid input for Terraformer.Point";
  834. }
  835. this.type = "Point";
  836. }
  837. Point.prototype = new Primitive();
  838. Point.prototype.constructor = Point;
  839. /*
  840. GeoJSON MultiPoint Class
  841. new MultiPoint();
  842. new MultiPoint([[x,y], [x1,y1]]);
  843. new MultiPoint({
  844. type: "MultiPoint",
  845. coordinates: [x,y]
  846. });
  847. */
  848. function MultiPoint(input){
  849. if(input && input.type === "MultiPoint" && input.coordinates){
  850. extend(this, input);
  851. } else if(isArray(input)) {
  852. this.coordinates = input;
  853. } else {
  854. throw "Terraformer: invalid input for Terraformer.MultiPoint";
  855. }
  856. this.type = "MultiPoint";
  857. }
  858. MultiPoint.prototype = new Primitive();
  859. MultiPoint.prototype.constructor = MultiPoint;
  860. MultiPoint.prototype.forEach = function(func){
  861. for (var i = 0; i < this.coordinates.length; i++) {
  862. func.apply(this, [this.coordinates[i], i, this.coordinates]);
  863. }
  864. return this;
  865. };
  866. MultiPoint.prototype.addPoint = function(point){
  867. this.coordinates.push(point);
  868. return this;
  869. };
  870. MultiPoint.prototype.insertPoint = function(point, index){
  871. this.coordinates.splice(index, 0, point);
  872. return this;
  873. };
  874. MultiPoint.prototype.removePoint = function(remove){
  875. if(typeof remove === "number"){
  876. this.coordinates.splice(remove, 1);
  877. } else {
  878. this.coordinates.splice(this.coordinates.indexOf(remove), 1);
  879. }
  880. return this;
  881. };
  882. MultiPoint.prototype.get = function(i){
  883. return new Point(this.coordinates[i]);
  884. };
  885. /*
  886. GeoJSON LineString Class
  887. new LineString();
  888. new LineString([[x,y], [x1,y1]]);
  889. new LineString({
  890. type: "LineString",
  891. coordinates: [x,y]
  892. });
  893. */
  894. function LineString(input){
  895. if(input && input.type === "LineString" && input.coordinates){
  896. extend(this, input);
  897. } else if(isArray(input)) {
  898. this.coordinates = input;
  899. } else {
  900. throw "Terraformer: invalid input for Terraformer.LineString";
  901. }
  902. this.type = "LineString";
  903. }
  904. LineString.prototype = new Primitive();
  905. LineString.prototype.constructor = LineString;
  906. LineString.prototype.addVertex = function(point){
  907. this.coordinates.push(point);
  908. return this;
  909. };
  910. LineString.prototype.insertVertex = function(point, index){
  911. this.coordinates.splice(index, 0, point);
  912. return this;
  913. };
  914. LineString.prototype.removeVertex = function(remove){
  915. this.coordinates.splice(remove, 1);
  916. return this;
  917. };
  918. /*
  919. GeoJSON MultiLineString Class
  920. new MultiLineString();
  921. new MultiLineString([ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ]);
  922. new MultiLineString({
  923. type: "MultiLineString",
  924. coordinates: [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ]
  925. });
  926. */
  927. function MultiLineString(input){
  928. if(input && input.type === "MultiLineString" && input.coordinates){
  929. extend(this, input);
  930. } else if(isArray(input)) {
  931. this.coordinates = input;
  932. } else {
  933. throw "Terraformer: invalid input for Terraformer.MultiLineString";
  934. }
  935. this.type = "MultiLineString";
  936. }
  937. MultiLineString.prototype = new Primitive();
  938. MultiLineString.prototype.constructor = MultiLineString;
  939. MultiLineString.prototype.forEach = function(func){
  940. for (var i = 0; i < this.coordinates.length; i++) {
  941. func.apply(this, [this.coordinates[i], i, this.coordinates ]);
  942. }
  943. };
  944. MultiLineString.prototype.get = function(i){
  945. return new LineString(this.coordinates[i]);
  946. };
  947. /*
  948. GeoJSON Polygon Class
  949. new Polygon();
  950. new Polygon([ [[x,y], [x1,y1], [x2,y2]] ]);
  951. new Polygon({
  952. type: "Polygon",
  953. coordinates: [ [[x,y], [x1,y1], [x2,y2]] ]
  954. });
  955. */
  956. function Polygon(input){
  957. if(input && input.type === "Polygon" && input.coordinates){
  958. extend(this, input);
  959. } else if(isArray(input)) {
  960. this.coordinates = input;
  961. } else {
  962. throw "Terraformer: invalid input for Terraformer.Polygon";
  963. }
  964. this.type = "Polygon";
  965. }
  966. Polygon.prototype = new Primitive();
  967. Polygon.prototype.constructor = Polygon;
  968. Polygon.prototype.addVertex = function(point){
  969. this.insertVertex(point, this.coordinates[0].length - 1);
  970. return this;
  971. };
  972. Polygon.prototype.insertVertex = function(point, index){
  973. this.coordinates[0].splice(index, 0, point);
  974. return this;
  975. };
  976. Polygon.prototype.removeVertex = function(remove){
  977. this.coordinates[0].splice(remove, 1);
  978. return this;
  979. };
  980. Polygon.prototype.close = function() {
  981. this.coordinates = closedPolygon(this.coordinates);
  982. };
  983. Polygon.prototype.hasHoles = function() {
  984. return this.coordinates.length > 1;
  985. };
  986. Polygon.prototype.holes = function() {
  987. var holes = [];
  988. if (this.hasHoles()) {
  989. for (var i = 1; i < this.coordinates.length; i++) {
  990. holes.push(new Polygon([this.coordinates[i]]));
  991. }
  992. }
  993. return holes;
  994. };
  995. /*
  996. GeoJSON MultiPolygon Class
  997. new MultiPolygon();
  998. new MultiPolygon([ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ]);
  999. new MultiPolygon({
  1000. type: "MultiPolygon",
  1001. coordinates: [ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ]
  1002. });
  1003. */
  1004. function MultiPolygon(input){
  1005. if(input && input.type === "MultiPolygon" && input.coordinates){
  1006. extend(this, input);
  1007. } else if(isArray(input)) {
  1008. this.coordinates = input;
  1009. } else {
  1010. throw "Terraformer: invalid input for Terraformer.MultiPolygon";
  1011. }
  1012. this.type = "MultiPolygon";
  1013. }
  1014. MultiPolygon.prototype = new Primitive();
  1015. MultiPolygon.prototype.constructor = MultiPolygon;
  1016. MultiPolygon.prototype.forEach = function(func){
  1017. for (var i = 0; i < this.coordinates.length; i++) {
  1018. func.apply(this, [this.coordinates[i], i, this.coordinates ]);
  1019. }
  1020. };
  1021. MultiPolygon.prototype.get = function(i){
  1022. return new Polygon(this.coordinates[i]);
  1023. };
  1024. MultiPolygon.prototype.close = function(){
  1025. var outer = [];
  1026. this.forEach(function(polygon){
  1027. outer.push(closedPolygon(polygon));
  1028. });
  1029. this.coordinates = outer;
  1030. return this;
  1031. };
  1032. /*
  1033. GeoJSON Feature Class
  1034. new Feature();
  1035. new Feature({
  1036. type: "Feature",
  1037. geometry: {
  1038. type: "Polygon",
  1039. coordinates: [ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ]
  1040. }
  1041. });
  1042. new Feature({
  1043. type: "Polygon",
  1044. coordinates: [ [ [[x,y], [x1,y1]], [[x2,y2], [x3,y3]] ] ]
  1045. });
  1046. */
  1047. function Feature(input){
  1048. if(input && input.type === "Feature"){
  1049. extend(this, input);
  1050. } else if(input && input.type && input.coordinates) {
  1051. this.geometry = input;
  1052. } else {
  1053. throw "Terraformer: invalid input for Terraformer.Feature";
  1054. }
  1055. this.type = "Feature";
  1056. }
  1057. Feature.prototype = new Primitive();
  1058. Feature.prototype.constructor = Feature;
  1059. /*
  1060. GeoJSON FeatureCollection Class
  1061. new FeatureCollection();
  1062. new FeatureCollection([feature, feature1]);
  1063. new FeatureCollection({
  1064. type: "FeatureCollection",
  1065. coordinates: [feature, feature1]
  1066. });
  1067. */
  1068. function FeatureCollection(input){
  1069. if(input && input.type === "FeatureCollection" && input.features){
  1070. extend(this, input);
  1071. } else if(isArray(input)) {
  1072. this.features = input;
  1073. } else {
  1074. throw "Terraformer: invalid input for Terraformer.FeatureCollection";
  1075. }
  1076. this.type = "FeatureCollection";
  1077. }
  1078. FeatureCollection.prototype = new Primitive();
  1079. FeatureCollection.prototype.constructor = FeatureCollection;
  1080. FeatureCollection.prototype.forEach = function(func){
  1081. for (var i = 0; i < this.features.length; i++) {
  1082. func.apply(this, [this.features[i], i, this.features]);
  1083. }
  1084. };
  1085. FeatureCollection.prototype.get = function(id){
  1086. var found;
  1087. this.forEach(function(feature){
  1088. if(feature.id === id){
  1089. found = feature;
  1090. }
  1091. });
  1092. return new Feature(found);
  1093. };
  1094. /*
  1095. GeoJSON GeometryCollection Class
  1096. new GeometryCollection();
  1097. new GeometryCollection([geometry, geometry1]);
  1098. new GeometryCollection({
  1099. type: "GeometryCollection",
  1100. coordinates: [geometry, geometry1]
  1101. });
  1102. */
  1103. function GeometryCollection(input){
  1104. if(input && input.type === "GeometryCollection" && input.geometries){
  1105. extend(this, input);
  1106. } else if(isArray(input)) {
  1107. this.geometries = input;
  1108. } else if(input.coordinates && input.type){
  1109. this.type = "GeometryCollection";
  1110. this.geometries = [input];
  1111. } else {
  1112. throw "Terraformer: invalid input for Terraformer.GeometryCollection";
  1113. }
  1114. this.type = "GeometryCollection";
  1115. }
  1116. GeometryCollection.prototype = new Primitive();
  1117. GeometryCollection.prototype.constructor = GeometryCollection;
  1118. GeometryCollection.prototype.forEach = function(func){
  1119. for (var i = 0; i < this.geometries.length; i++) {
  1120. func.apply(this, [this.geometries[i], i, this.geometries]);
  1121. }
  1122. };
  1123. GeometryCollection.prototype.get = function(i){
  1124. return new Primitive(this.geometries[i]);
  1125. };
  1126. function createCircle(center, radius, interpolate){
  1127. var mercatorPosition = positionToMercator(center);
  1128. var steps = interpolate || 64;
  1129. var polygon = {
  1130. type: "Polygon",
  1131. coordinates: [[]]
  1132. };
  1133. for(var i=1; i<=steps; i++) {
  1134. var radians = i * (360/steps) * Math.PI / 180;
  1135. polygon.coordinates[0].push([mercatorPosition[0] + radius * Math.cos(radians), mercatorPosition[1] + radius * Math.sin(radians)]);
  1136. }
  1137. polygon.coordinates = closedPolygon(polygon.coordinates);
  1138. return toGeographic(polygon);
  1139. }
  1140. function Circle (center, radius, interpolate) {
  1141. var steps = interpolate || 64;
  1142. var rad = radius || 250;
  1143. if(!center || center.length < 2 || !rad || !steps) {
  1144. throw new Error("Terraformer: missing parameter for Terraformer.Circle");
  1145. }
  1146. extend(this, new Feature({
  1147. type: "Feature",
  1148. geometry: createCircle(center, rad, steps),
  1149. properties: {
  1150. radius: rad,
  1151. center: center,
  1152. steps: steps
  1153. }
  1154. }));
  1155. }
  1156. Circle.prototype = new Primitive();
  1157. Circle.prototype.constructor = Circle;
  1158. Circle.prototype.recalculate = function(){
  1159. this.geometry = createCircle(this.properties.center, this.properties.radius, this.properties.steps);
  1160. return this;
  1161. };
  1162. Circle.prototype.center = function(coordinates){
  1163. if(coordinates){
  1164. this.properties.center = coordinates;
  1165. this.recalculate();
  1166. }
  1167. return this.properties.center;
  1168. };
  1169. Circle.prototype.radius = function(radius){
  1170. if(radius){
  1171. this.properties.radius = radius;
  1172. this.recalculate();
  1173. }
  1174. return this.properties.radius;
  1175. };
  1176. Circle.prototype.steps = function(steps){
  1177. if(steps){
  1178. this.properties.steps = steps;
  1179. this.recalculate();
  1180. }
  1181. return this.properties.steps;
  1182. };
  1183. Circle.prototype.toJSON = function() {
  1184. var output = Primitive.prototype.toJSON.call(this);
  1185. return output;
  1186. };
  1187. exports.Primitive = Primitive;
  1188. exports.Point = Point;
  1189. exports.MultiPoint = MultiPoint;
  1190. exports.LineString = LineString;
  1191. exports.MultiLineString = MultiLineString;
  1192. exports.Polygon = Polygon;
  1193. exports.MultiPolygon = MultiPolygon;
  1194. exports.Feature = Feature;
  1195. exports.FeatureCollection = FeatureCollection;
  1196. exports.GeometryCollection = GeometryCollection;
  1197. exports.Circle = Circle;
  1198. exports.toMercator = toMercator;
  1199. exports.toGeographic = toGeographic;
  1200. exports.Tools = {};
  1201. exports.Tools.positionToMercator = positionToMercator;
  1202. exports.Tools.positionToGeographic = positionToGeographic;
  1203. exports.Tools.applyConverter = applyConverter;
  1204. exports.Tools.toMercator = toMercator;
  1205. exports.Tools.toGeographic = toGeographic;
  1206. exports.Tools.createCircle = createCircle;
  1207. exports.Tools.calculateBounds = calculateBounds;
  1208. exports.Tools.calculateEnvelope = calculateEnvelope;
  1209. exports.Tools.coordinatesContainPoint = coordinatesContainPoint;
  1210. exports.Tools.polygonContainsPoint = polygonContainsPoint;
  1211. exports.Tools.arraysIntersectArrays = arraysIntersectArrays;
  1212. exports.Tools.coordinatesContainPoint = coordinatesContainPoint;
  1213. exports.Tools.coordinatesEqual = coordinatesEqual;
  1214. exports.Tools.convexHull = convexHull;
  1215. exports.Tools.isConvex = isConvex;
  1216. exports.MercatorCRS = MercatorCRS;
  1217. exports.GeographicCRS = GeographicCRS;
  1218. return exports;
  1219. }));