WME Util Singleton

Utility singleton for Waze map editor scripts

目前為 2016-01-01 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/15731/98371/WME%20Util%20Singleton.js

  1. // ==UserScript==
  2. // @name WME Util Singleton
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.201
  5. // @description Utility singleton for Waze map editor scripts
  6. // @author slemmon
  7. // @match https://www.waze.com/editor/*
  8. // @match https://editor-beta.waze.com/editor/*
  9. // @grant none
  10. // ==/UserScript==
  11. /* jshint -W097 */
  12. 'use strict';
  13.  
  14. /**
  15. * =============================================
  16. * USER INFORMATION
  17. * =============================================
  18. *
  19. * This is a util class to be @required by other scripts. Consider the API a bit fluid at
  20. * this stage as this class is new and not very fleshed out. Documentation for each class
  21. * method / property will appear just before the method / property.
  22. *
  23. * The Ext JS framework is loaded and once loaded the W.ux.Util singleton is
  24. * created. Next we fire a jQuery event of "extready" into the document body
  25. * so that any scripts waiting on Ext JS and the W.ux.Util singleton know they
  26. * may proceed.
  27. *
  28. * Scripts using W.ux.Util should add the following to listen for the 'extready'
  29. * event:
  30. * $(document.body).on('extready', function () {
  31. * // script logic - may still need to check for dom ready and / or
  32. * // the OpenLayers map's existence here before proceeding
  33. * });
  34. */
  35. $.getScript('https://cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all-debug.js', function () {
  36. Ext.define('W.ux.Util', {
  37. /**
  38. * Adds CSS style rule(s) to the page
  39. * @param {String/String[]/String...} rule An array of CSS style rule strings or
  40. * any number of CSS style rule params
  41. */
  42. injectStyleRules(rule) {
  43. var styleTag = this.styleTag,
  44. args = Array.prototype.slice.call(arguments),
  45. div;
  46. // allows you to pass in an array, string, or n number of string params
  47. rule = args.length === 1 ? args.shift() : args;
  48. // if the style tag is found cached then create at the end of the <body>
  49. // and cache a reference to it
  50. if (!styleTag) {
  51. div = $("<div />", {
  52. html: '&shy;<style></style>'
  53. }).appendTo("body");
  54. // cache a ref to the style tag
  55. styleTag = this.styleTag = div.find('style');
  56. }
  57. // append to the style tag the style rule / rules passed
  58. styleTag.append(Ext.Array.from(rule).join(''));
  59. },
  60. /**
  61. * Returns the Waze editor map or false if the map is not found
  62. * @return {Object/Boolean} The map instance used by Waze or false if not found
  63. */
  64. getMap: function () {
  65. return (W && W.map) ? W.map : false;
  66. },
  67. /**
  68. * Simple util to return an array of [a, b] type endpoint sub-arrays given a complete
  69. * array of points (vertices) from a ring polygon geometry. For example, if you passed:
  70. * [x, y, z] in what would be returned is [[x, y], [y, z], [z, x]].
  71. * @param {Array} points Array of all points in the ring polygon geometry
  72. * @return {Array} An array of endpoint arrays from the passed points
  73. */
  74. getEndpoints: function (points) {
  75. var endpoints = [],
  76. len = points.length,
  77. pointB;
  78. points.forEach(function (point, i) {
  79. var pointB = (points[i + 1]) ? points[i + 1] : points[0];
  80. endpoints.push([points[i], pointB]);
  81. });
  82. return endpoints;
  83. },
  84. // http://jsfiddle.net/justin_c_rounds/Gd2S2/
  85. /**
  86. * Finds the points where two lines intersect. That could be intersections where
  87. * the lines would literally overlap or could be where the extension of a line would
  88. * logically overlap.
  89. * @param {Number} line1StartX The starting x coordinate of line 1
  90. * @param {Number} line1StartY The starting y coordinate of line 1
  91. * @param {Number} line1EndX The ending x coordinate of line 1
  92. * @param {Number} line1EndY The ending y coordinate of line 1
  93. * @param {Number} line2StartX The starting x coordinate of line 2
  94. * @param {Number} line2StartY The starting y coordinate of line 2
  95. * @param {Number} line2EndX The ending x coordinate of line 2
  96. * @param {Number} line2EndY The ending y coordinate of line 2
  97. * @return {Object} Returns an object with the following key / value info
  98. *
  99. * - x {Number} the x coordinate of the intersection or null if there is no intersection
  100. * - y {Number} the y coordinate of the intersection or null if there is no intersection
  101. * - onLine1 {Boolean} true if the intersection falls on line 1 otherwise false
  102. * - onLine2 {Boolean} true if the intersection falls on line 2 otherwise false
  103. */
  104. findIntersectionPoint: function (line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
  105. // if the lines intersect, the result contains the x and y of the intersection
  106. // (treating the lines as infinite) and booleans for whether line segment 1
  107. // or line segment 2 contain the point
  108. var denominator, a, b, numerator1, numerator2, result = {
  109. x: null,
  110. y: null,
  111. onLine1: false,
  112. onLine2: false
  113. };
  114. // we'll assume two arrays of two points were passed so we'll split them out
  115. if (Ext.isArray(line1StartX)) {
  116. line2EndY = line1StartY[1].y;
  117. line2EndX = line1StartY[1].x;
  118. line2StartY = line1StartY[0].y;
  119. line2StartX = line1StartY[0].x;
  120. line1EndY = line1StartX[1].y;
  121. line1EndX = line1StartX[1].x;
  122. line1StartY = line1StartX[0].y;
  123. line1StartX = line1StartX[0].x;
  124. }
  125.  
  126. denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
  127. if (denominator == 0) {
  128. return result;
  129. }
  130. a = line1StartY - line2StartY;
  131. b = line1StartX - line2StartX;
  132. numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
  133. numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
  134. a = numerator1 / denominator;
  135. b = numerator2 / denominator;
  136.  
  137. // if we cast these lines infinitely in both directions, they intersect here:
  138. result.x = line1StartX + (a * (line1EndX - line1StartX));
  139. result.y = line1StartY + (a * (line1EndY - line1StartY));
  140. /*
  141. // it is worth noting that this should be the same as:
  142. x = line2StartX + (b * (line2EndX - line2StartX));
  143. y = line2StartX + (b * (line2EndY - line2StartY));
  144. */
  145. // if line1 is a segment and line2 is infinite, they intersect if:
  146. if (a > 0 && a < 1) {
  147. result.onLine1 = true;
  148. }
  149. // if line2 is a segment and line1 is infinite, they intersect if:
  150. if (b > 0 && b < 1) {
  151. result.onLine2 = true;
  152. }
  153. // if line1 and line2 are segments, they intersect if both of the above are true
  154. return result;
  155. },
  156. /**
  157. * Returns the first two intersecting features from a layer
  158. * @param {Object} layer The layer from which to find intersecting features
  159. * @return {Array} An array containing the first two intersecting features
  160. * found or an empty array if no intersection features were found
  161. */
  162. getIntersection: function (layer) {
  163. if (!layer) {
  164. return false;
  165. }
  166. var features = layer.features,
  167. len = features.length,
  168. intersected = [],
  169. i = 0,
  170. j, feature, featureGeo, candidate, candidateGeo, intersects;
  171. for (; i < len; i++) {
  172. feature = features[i];
  173. featureGeo = feature.geometry;
  174. j = 0;
  175. for (; j < len; j++) {
  176. candidate = features[j];
  177. candidateGeo = candidate.geometry;
  178. intersects = featureGeo.intersects(candidateGeo);
  179. if (intersects && (featureGeo !== candidateGeo)) {
  180. intersected = [feature, candidate];
  181. break;
  182. }
  183. }
  184. if (intersected.length) {
  185. break;
  186. }
  187. }
  188. return intersected;
  189. },
  190. /**
  191. * Get a new polygon feature from an array of points
  192. * @param {Array} points The array of OpenLayers.Geometry.Points used to make the polygon feature
  193. * @param {Object} [attribs] Optional object to be mapped to the attributes property of the feature
  194. * @param {Object} [style] Optional style object
  195. * @return {Object} The polygon feature generaeted from the provided points
  196. */
  197. getPolyFromPoints: function (points, attribs, style) {
  198. var ring = new OpenLayers.Geometry.LinearRing(points),
  199. polygon = new OpenLayers.Geometry.Polygon([ring]),
  200. feature = new OpenLayers.Feature.Vector(polygon, attribs, style);
  201.  
  202. return feature;
  203. },
  204. /**
  205. * @private
  206. * Internal method used by the getMergedPolygon method
  207. */
  208. applyIntersectionPoints: function (pointsArrA, pointsArrB, altPoly, segmentsArrA, segmentsArrB, copyPoints) {
  209. var me = this,
  210. intersect, junction;
  211. pointsArrA.forEach(function (first, i) {
  212. first.isInternal = altPoly.containsPoint(first);
  213. delete first.isJunction;
  214. var junctions = [];
  215. pointsArrB.forEach(function (second, j) {
  216. intersect = me.findIntersectionPoint(segmentsArrA[i], segmentsArrB[j]);
  217. if (intersect.onLine1 && intersect.onLine2) {
  218. junction = new OpenLayers.Geometry.Point(intersect.x, intersect.y);
  219. junction.isJunction = true;
  220. junctions.unshift(junction);
  221. }
  222. });
  223. junctions.sort(function (a, b) {
  224. var aDist = first.distanceTo(a),
  225. bDist = first.distanceTo(b);
  226.  
  227. return aDist > bDist ? -1 : ((bDist > aDist) ? 1 : 0);
  228. });
  229. junctions.forEach(function (item) {
  230. copyPoints.splice(copyPoints.indexOf(pointsArrA[i]) + 1, 0, item);
  231. });
  232. });
  233. },
  234. /**
  235. * Return a single polygon feature from the outline of two intersecting
  236. * polygons
  237. * @param {Object/Object[]} polyA The first polygon or feataure owning the polygon
  238. * to be merged. May also be an array of both constituent polygons in which case
  239. * the second param if passed will be ignored
  240. * @param {Object} polyB The second polygon or feature owning the polygon to
  241. * be merged
  242. * return {Object} The merged polygon vector feature
  243. */
  244. getMergedPolygon: function (polyA, polyB) {
  245. if (this.isArray(polyA)) {
  246. polyB = polyA[1];
  247. polyA = polyA[0];
  248. }
  249. // set polyA and B to be the polygon geometries if the parent vector feature was passed in
  250. polyA = polyA.CLASS_NAME === "OpenLayers.Feature.Vector" ? polyA.geometry : polyA;
  251. polyB = polyB.CLASS_NAME === "OpenLayers.Feature.Vector" ? polyB.geometry : polyB;
  252. var me = this,
  253. pointsA = polyA.getVertices(),
  254. pointsB = polyB.getVertices(),
  255. copyPointsA = pointsA.slice(),
  256. copyPointsB = pointsB.slice(),
  257. segmentsA = me.getEndpoints(pointsA),
  258. segmentsB = me.getEndpoints(pointsB),
  259. initialX = Infinity,
  260. initial, activePoints, hostPoly, activePoint, union, altPoints,
  261. activeIndex, next, nextAltIndex, i, altNext, altA, altB,
  262. centroidA, candidateAIntersects, centroidB, candidateBIntersects,
  263. mids, mid;
  264. // Set OpenLayers.geometry.Points in the points / vertices array where the two
  265. // polygons intersect
  266. me.applyIntersectionPoints(pointsA, pointsB, polyB, segmentsA, segmentsB, copyPointsA);
  267. me.applyIntersectionPoints(pointsB, pointsA, polyA, segmentsB, segmentsA, copyPointsB);
  268.  
  269. // function to find the starting point for when we walk through the polygon to get
  270. // its outline. The initial point will be the one that's furthest left
  271. (function () {
  272. var evalPoints = function (points) {
  273. points.forEach(function (point) {
  274. if (point.x < initialX) {
  275. initialX = point.x;
  276. initial = point;
  277. activePoints = points;
  278. hostPoly = (activePoints === copyPointsA) ? polyA : polyB;
  279. }
  280. });
  281. };
  282.  
  283. evalPoints(copyPointsA);
  284. evalPoints(copyPointsB);
  285. })();
  286.  
  287. // the union array will hold the points of the new polygon perimeter
  288. union = [initial];
  289. altPoints = (activePoints === copyPointsA) ? copyPointsB : copyPointsA;
  290. while (activePoint !== initial) {
  291. activePoint = activePoint || initial;
  292.  
  293. // find the current pointer and the next point from it
  294. activeIndex = activePoints.indexOf(activePoint);
  295. next = activePoints[activeIndex + 1] || activePoints[0];
  296. // if the next point is not a junction add it to the union array
  297. if (!next.isJunction) {
  298. if (next !== initial) {
  299. union.push(next);
  300. }
  301. activePoint = next;
  302. } else { // the point is a junction / intersect point
  303. union.push(next); // add it to the union array
  304. nextAltIndex;
  305. // find the next perimeter junction
  306. for (i = 0; i < altPoints.length; i++) {
  307. if (altPoints[i].x === next.x && altPoints[i].y === next.y) {
  308. nextAltIndex = i;
  309. break;
  310. }
  311. }
  312.  
  313. // using the next junction find the point ahead and behind it
  314. altNext = altPoints[nextAltIndex];
  315. altA = altPoints[nextAltIndex - 1] || altPoints[altPoints.length - 1];
  316. altB = altPoints[nextAltIndex + 1] || altPoints[0];
  317.  
  318. // see if the point behind it crosses the initial polygon
  319. centroidA = new OpenLayers.Geometry.Point((altNext.x + altA.x)/2, (altNext.y + altA.y)/2);
  320. candidateAIntersects = hostPoly.containsPoint(centroidA);
  321. centroidA.destroy();
  322.  
  323. // see if the point ahead of it crosses the initial polygon
  324. centroidB = new OpenLayers.Geometry.Point((altNext.x + altB.x)/2, (altNext.y + altB.y)/2);
  325. candidateBIntersects = hostPoly.containsPoint(centroidB);
  326. centroidB.destroy();
  327.  
  328. // the mids array will be the points from the companion polygon
  329. // between the two intersections currently being inspected
  330. mids = [];
  331. mid = {};
  332. //if one of the lines does not intersect (its posible for there
  333. // to be no points between the junctions)
  334. if (!candidateAIntersects || !candidateBIntersects) {
  335. i = nextAltIndex;
  336. // find all the points between the junctions and add them to the
  337. // mids array to be then added to the union array
  338. while (!mid.isJunction) {
  339. if (!candidateAIntersects) {
  340. i = (i - 1 > -1) ? i - 1 : altPoints.length - 1;
  341. } else {
  342. i = (i + 1 < altPoints.length) ? i + 1 : 0;
  343. }
  344. mid = altPoints[i];
  345. if (mid && !mid.isJunction) {
  346. mids.push(mid);
  347. }
  348. }
  349. }
  350.  
  351. // add the points between the junctions from the companion polygon
  352. union = union.concat(mids);
  353.  
  354. // if we're at the junction add the junction point corresponding within
  355. // the initial polygon
  356. if (mid.isJunction) {
  357. for (i = 0; i < activePoints.length; i++) {
  358. if (activePoints[i].x === mid.x && activePoints[i].y === mid.y) {
  359. activePoint = activePoints[i];
  360. union.push(activePoint);
  361. break;
  362. }
  363. }
  364. }
  365. }
  366. // continue the loop until all points from the initial and companion polygon
  367. // have been included to create a merged perimeter
  368. }
  369.  
  370. return this.getPolyFromPoints(union);
  371. }
  372. });
  373. // this announces to any listening scripts that Ext and the W.ux.Util
  374. // singleton are now ready for use
  375. $(document.body).trigger('extready');
  376. });