PageRenderTime 44ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/js/data/feature_index.js

https://gitlab.com/hwhelchel/mapbox-gl-js
JavaScript | 297 lines | 239 code | 49 blank | 9 comment | 44 complexity | 8baf94ec4b323f77b2d9ba5a168b4cee MD5 | raw file
  1. 'use strict';
  2. var Point = require('point-geometry');
  3. var loadGeometry = require('./load_geometry');
  4. var EXTENT = require('./bucket').EXTENT;
  5. var featureFilter = require('feature-filter');
  6. var StructArrayType = require('../util/struct_array');
  7. var Grid = require('grid-index');
  8. var DictionaryCoder = require('../util/dictionary_coder');
  9. var vt = require('vector-tile');
  10. var Protobuf = require('pbf');
  11. var GeoJSONFeature = require('../util/vectortile_to_geojson');
  12. var arraysIntersect = require('../util/util').arraysIntersect;
  13. var intersection = require('../util/intersection_tests');
  14. var multiPolygonIntersectsBufferedMultiPoint = intersection.multiPolygonIntersectsBufferedMultiPoint;
  15. var multiPolygonIntersectsMultiPolygon = intersection.multiPolygonIntersectsMultiPolygon;
  16. var multiPolygonIntersectsBufferedMultiLine = intersection.multiPolygonIntersectsBufferedMultiLine;
  17. var FeatureIndexArray = new StructArrayType({
  18. members: [
  19. // the index of the feature in the original vectortile
  20. { type: 'Uint32', name: 'featureIndex' },
  21. // the source layer the feature appears in
  22. { type: 'Uint16', name: 'sourceLayerIndex' },
  23. // the bucket the feature appears in
  24. { type: 'Uint16', name: 'bucketIndex' }
  25. ]});
  26. module.exports = FeatureIndex;
  27. function FeatureIndex(coord, overscaling, collisionTile) {
  28. if (coord.grid) {
  29. var serialized = coord;
  30. var rawTileData = overscaling;
  31. coord = serialized.coord;
  32. overscaling = serialized.overscaling;
  33. this.grid = new Grid(serialized.grid);
  34. this.featureIndexArray = new FeatureIndexArray(serialized.featureIndexArray);
  35. this.rawTileData = rawTileData;
  36. this.bucketLayerIDs = serialized.bucketLayerIDs;
  37. } else {
  38. this.grid = new Grid(EXTENT, 16, 0);
  39. this.featureIndexArray = new FeatureIndexArray();
  40. }
  41. this.coord = coord;
  42. this.overscaling = overscaling;
  43. this.x = coord.x;
  44. this.y = coord.y;
  45. this.z = coord.z - Math.log(overscaling) / Math.LN2;
  46. this.setCollisionTile(collisionTile);
  47. }
  48. FeatureIndex.prototype.insert = function(feature, featureIndex, sourceLayerIndex, bucketIndex) {
  49. var key = this.featureIndexArray.length;
  50. this.featureIndexArray.emplaceBack(featureIndex, sourceLayerIndex, bucketIndex);
  51. var geometry = loadGeometry(feature);
  52. for (var r = 0; r < geometry.length; r++) {
  53. var ring = geometry[r];
  54. var bbox = [Infinity, Infinity, -Infinity, -Infinity];
  55. for (var i = 0; i < ring.length; i++) {
  56. var p = ring[i];
  57. bbox[0] = Math.min(bbox[0], p.x);
  58. bbox[1] = Math.min(bbox[1], p.y);
  59. bbox[2] = Math.max(bbox[2], p.x);
  60. bbox[3] = Math.max(bbox[3], p.y);
  61. }
  62. this.grid.insert(key, bbox[0], bbox[1], bbox[2], bbox[3]);
  63. }
  64. };
  65. FeatureIndex.prototype.setCollisionTile = function(collisionTile) {
  66. this.collisionTile = collisionTile;
  67. };
  68. FeatureIndex.prototype.serialize = function() {
  69. var data = {
  70. coord: this.coord,
  71. overscaling: this.overscaling,
  72. grid: this.grid.toArrayBuffer(),
  73. featureIndexArray: this.featureIndexArray.serialize(),
  74. bucketLayerIDs: this.bucketLayerIDs
  75. };
  76. return {
  77. data: data,
  78. transferables: [data.grid, data.featureIndexArray.arrayBuffer]
  79. };
  80. };
  81. function translateDistance(translate) {
  82. return Math.sqrt(translate[0] * translate[0] + translate[1] * translate[1]);
  83. }
  84. // Finds features in this tile at a particular position.
  85. FeatureIndex.prototype.query = function(args, styleLayers) {
  86. if (!this.vtLayers) {
  87. this.vtLayers = new vt.VectorTile(new Protobuf(new Uint8Array(this.rawTileData))).layers;
  88. this.sourceLayerCoder = new DictionaryCoder(this.vtLayers ? Object.keys(this.vtLayers).sort() : ['_geojsonTileLayer']);
  89. }
  90. var result = {};
  91. var params = args.params || {},
  92. pixelsToTileUnits = EXTENT / args.tileSize / args.scale,
  93. filter = featureFilter(params.filter);
  94. // Features are indexed their original geometries. The rendered geometries may
  95. // be buffered, translated or offset. Figure out how much the search radius needs to be
  96. // expanded by to include these features.
  97. var additionalRadius = 0;
  98. for (var id in styleLayers) {
  99. var styleLayer = styleLayers[id];
  100. var paint = styleLayer.paint;
  101. var styleLayerDistance = 0;
  102. if (styleLayer.type === 'line') {
  103. styleLayerDistance = getLineWidth(paint) / 2 + Math.abs(paint['line-offset']) + translateDistance(paint['line-translate']);
  104. } else if (styleLayer.type === 'fill') {
  105. styleLayerDistance = translateDistance(paint['fill-translate']);
  106. } else if (styleLayer.type === 'circle') {
  107. styleLayerDistance = paint['circle-radius'] + translateDistance(paint['circle-translate']);
  108. }
  109. additionalRadius = Math.max(additionalRadius, styleLayerDistance * pixelsToTileUnits);
  110. }
  111. var queryGeometry = args.queryGeometry.map(function(q) {
  112. return q.map(function(p) {
  113. return new Point(p.x, p.y);
  114. });
  115. });
  116. var minX = Infinity;
  117. var minY = Infinity;
  118. var maxX = -Infinity;
  119. var maxY = -Infinity;
  120. for (var i = 0; i < queryGeometry.length; i++) {
  121. var ring = queryGeometry[i];
  122. for (var k = 0; k < ring.length; k++) {
  123. var p = ring[k];
  124. minX = Math.min(minX, p.x);
  125. minY = Math.min(minY, p.y);
  126. maxX = Math.max(maxX, p.x);
  127. maxY = Math.max(maxY, p.y);
  128. }
  129. }
  130. var matching = this.grid.query(minX - additionalRadius, minY - additionalRadius, maxX + additionalRadius, maxY + additionalRadius);
  131. matching.sort(topDownFeatureComparator);
  132. this.filterMatching(result, matching, this.featureIndexArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits);
  133. var matchingSymbols = this.collisionTile.queryRenderedSymbols(minX, minY, maxX, maxY, args.scale);
  134. matchingSymbols.sort();
  135. this.filterMatching(result, matchingSymbols, this.collisionTile.collisionBoxArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits);
  136. return result;
  137. };
  138. function topDownFeatureComparator(a, b) {
  139. return b - a;
  140. }
  141. function getLineWidth(paint) {
  142. if (paint['line-gap-width'] > 0) {
  143. return paint['line-gap-width'] + 2 * paint['line-width'];
  144. } else {
  145. return paint['line-width'];
  146. }
  147. }
  148. FeatureIndex.prototype.filterMatching = function(result, matching, array, queryGeometry, filter, filterLayerIDs, styleLayers, bearing, pixelsToTileUnits) {
  149. var previousIndex;
  150. for (var k = 0; k < matching.length; k++) {
  151. var index = matching[k];
  152. // don't check the same feature more than once
  153. if (index === previousIndex) continue;
  154. previousIndex = index;
  155. var match = array.get(index);
  156. var layerIDs = this.bucketLayerIDs[match.bucketIndex];
  157. if (filterLayerIDs && !arraysIntersect(filterLayerIDs, layerIDs)) continue;
  158. var sourceLayerName = this.sourceLayerCoder.decode(match.sourceLayerIndex);
  159. var sourceLayer = this.vtLayers[sourceLayerName];
  160. var feature = sourceLayer.feature(match.featureIndex);
  161. if (!filter(feature)) continue;
  162. var geometry = null;
  163. for (var l = 0; l < layerIDs.length; l++) {
  164. var layerID = layerIDs[l];
  165. if (filterLayerIDs && filterLayerIDs.indexOf(layerID) < 0) {
  166. continue;
  167. }
  168. var styleLayer = styleLayers[layerID];
  169. if (!styleLayer) continue;
  170. var translatedPolygon;
  171. if (styleLayer.type !== 'symbol') {
  172. // all symbols already match the style
  173. if (!geometry) geometry = loadGeometry(feature);
  174. var paint = styleLayer.paint;
  175. if (styleLayer.type === 'line') {
  176. translatedPolygon = translate(queryGeometry,
  177. paint['line-translate'], paint['line-translate-anchor'],
  178. bearing, pixelsToTileUnits);
  179. var halfWidth = getLineWidth(paint) / 2 * pixelsToTileUnits;
  180. if (paint['line-offset']) {
  181. geometry = offsetLine(geometry, paint['line-offset'] * pixelsToTileUnits);
  182. }
  183. if (!multiPolygonIntersectsBufferedMultiLine(translatedPolygon, geometry, halfWidth)) continue;
  184. } else if (styleLayer.type === 'fill') {
  185. translatedPolygon = translate(queryGeometry,
  186. paint['fill-translate'], paint['fill-translate-anchor'],
  187. bearing, pixelsToTileUnits);
  188. if (!multiPolygonIntersectsMultiPolygon(translatedPolygon, geometry)) continue;
  189. } else if (styleLayer.type === 'circle') {
  190. translatedPolygon = translate(queryGeometry,
  191. paint['circle-translate'], paint['circle-translate-anchor'],
  192. bearing, pixelsToTileUnits);
  193. var circleRadius = paint['circle-radius'] * pixelsToTileUnits;
  194. if (!multiPolygonIntersectsBufferedMultiPoint(translatedPolygon, geometry, circleRadius)) continue;
  195. }
  196. }
  197. var geojsonFeature = new GeoJSONFeature(feature, this.z, this.x, this.y);
  198. geojsonFeature.layer = styleLayer.serialize({
  199. includeRefProperties: true
  200. });
  201. var layerResult = result[layerID];
  202. if (layerResult === undefined) {
  203. layerResult = result[layerID] = [];
  204. }
  205. layerResult.push(geojsonFeature);
  206. }
  207. }
  208. };
  209. function translate(queryGeometry, translate, translateAnchor, bearing, pixelsToTileUnits) {
  210. if (!translate[0] && !translate[1]) {
  211. return queryGeometry;
  212. }
  213. translate = Point.convert(translate);
  214. if (translateAnchor === "viewport") {
  215. translate._rotate(-bearing);
  216. }
  217. var translated = [];
  218. for (var i = 0; i < queryGeometry.length; i++) {
  219. var ring = queryGeometry[i];
  220. var translatedRing = [];
  221. for (var k = 0; k < ring.length; k++) {
  222. translatedRing.push(ring[k].sub(translate._mult(pixelsToTileUnits)));
  223. }
  224. translated.push(translatedRing);
  225. }
  226. return translated;
  227. }
  228. function offsetLine(rings, offset) {
  229. var newRings = [];
  230. var zero = new Point(0, 0);
  231. for (var k = 0; k < rings.length; k++) {
  232. var ring = rings[k];
  233. var newRing = [];
  234. for (var i = 0; i < ring.length; i++) {
  235. var a = ring[i - 1];
  236. var b = ring[i];
  237. var c = ring[i + 1];
  238. var aToB = i === 0 ? zero : b.sub(a)._unit()._perp();
  239. var bToC = i === ring.length - 1 ? zero : c.sub(b)._unit()._perp();
  240. var extrude = aToB._add(bToC)._unit();
  241. var cosHalfAngle = extrude.x * bToC.x + extrude.y * bToC.y;
  242. extrude._mult(1 / cosHalfAngle);
  243. newRing.push(extrude._mult(offset)._add(b));
  244. }
  245. newRings.push(newRing);
  246. }
  247. return newRings;
  248. }