Skip to content

Commit

Permalink
rename FeatureTree to FeatureIndex
Browse files Browse the repository at this point in the history
and split intersection tests into a separate file
  • Loading branch information
ansis committed Mar 24, 2016
1 parent bf1a15e commit 6e8bcef
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 177 deletions.
177 changes: 13 additions & 164 deletions js/data/feature_tree.js → js/data/feature_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ var Protobuf = require('pbf');
var GeoJSONFeature = require('../util/vectortile_to_geojson');
var arraysIntersect = require('../util/util').arraysIntersect;

var intersection = require('../util/intersection_tests');
var multiPolygonIntersectsBufferedMultiPoint = intersection.multiPolygonIntersectsBufferedMultiPoint;
var multiPolygonIntersectsMultiPolygon = intersection.multiPolygonIntersectsMultiPolygon;
var multiPolygonIntersectsBufferedMultiLine = intersection.multiPolygonIntersectsBufferedMultiLine;


var FeatureIndexArray = new StructArrayType({
members: [
// the index of the feature in the original vectortile
Expand All @@ -22,9 +28,9 @@ var FeatureIndexArray = new StructArrayType({
{ type: 'Uint16', name: 'bucketIndex' }
]});

module.exports = FeatureTree;
module.exports = FeatureIndex;

function FeatureTree(coord, overscaling, collisionTile) {
function FeatureIndex(coord, overscaling, collisionTile) {
if (coord.grid) {
var serialized = coord;
var rawTileData = overscaling;
Expand All @@ -46,7 +52,7 @@ function FeatureTree(coord, overscaling, collisionTile) {
this.setCollisionTile(collisionTile);
}

FeatureTree.prototype.insert = function(feature, featureIndex, sourceLayerIndex, bucketIndex) {
FeatureIndex.prototype.insert = function(feature, featureIndex, sourceLayerIndex, bucketIndex) {
var key = this.featureIndexArray.length;
this.featureIndexArray.emplaceBack(featureIndex, sourceLayerIndex, bucketIndex);
var geometry = loadGeometry(feature);
Expand All @@ -69,11 +75,11 @@ FeatureTree.prototype.insert = function(feature, featureIndex, sourceLayerIndex,
}
};

FeatureTree.prototype.setCollisionTile = function(collisionTile) {
FeatureIndex.prototype.setCollisionTile = function(collisionTile) {
this.collisionTile = collisionTile;
};

FeatureTree.prototype.serialize = function() {
FeatureIndex.prototype.serialize = function() {
var data = {
coord: this.coord,
overscaling: this.overscaling,
Expand All @@ -92,7 +98,7 @@ function translateDistance(translate) {
}

// Finds features in this tile at a particular position.
FeatureTree.prototype.query = function(args, styleLayers) {
FeatureIndex.prototype.query = function(args, styleLayers) {
if (!this.vtLayers) {
this.vtLayers = new vt.VectorTile(new Protobuf(new Uint8Array(this.rawTileData))).layers;
this.sourceLayerCoder = new DictionaryCoder(this.vtLayers ? Object.keys(this.vtLayers).sort() : ['_geojsonTileLayer']);
Expand Down Expand Up @@ -167,7 +173,7 @@ function getLineWidth(paint) {
}
}

FeatureTree.prototype.filterMatching = function(result, matching, array, queryGeometry, filter, filterLayerIDs, styleLayers, bearing, pixelsToTileUnits) {
FeatureIndex.prototype.filterMatching = function(result, matching, array, queryGeometry, filter, filterLayerIDs, styleLayers, bearing, pixelsToTileUnits) {
var previousIndex;
for (var k = 0; k < matching.length; k++) {
var index = matching[k];
Expand Down Expand Up @@ -291,160 +297,3 @@ function offsetLine(rings, offset) {
}
return newRings;
}

function multiPolygonIntersectsBufferedMultiPoint(multiPolygon, rings, radius) {
for (var j = 0; j < multiPolygon.length; j++) {
var polygon = multiPolygon[j];
for (var i = 0; i < rings.length; i++) {
var ring = rings[i];
for (var k = 0; k < ring.length; k++) {
var point = ring[k];
if (polygonContainsPoint(polygon, point)) return true;
if (pointIntersectsBufferedLine(point, polygon, radius)) return true;
}
}
}
return false;
}

function multiPolygonIntersectsMultiPolygon(multiPolygonA, multiPolygonB) {

if (multiPolygonA.length === 1 && multiPolygonA[0].length === 1) {
return multiPolygonContainsPoint(multiPolygonB, multiPolygonA[0][0]);
}

for (var m = 0; m < multiPolygonB.length; m++) {
var ring = multiPolygonB[m];
for (var n = 0; n < ring.length; n++) {
if (multiPolygonContainsPoint(multiPolygonA, ring[n])) return true;
}
}

for (var j = 0; j < multiPolygonA.length; j++) {
var polygon = multiPolygonA[j];
for (var i = 0; i < polygon.length; i++) {
if (multiPolygonContainsPoint(multiPolygonB, polygon[i])) return true;
}

for (var k = 0; k < multiPolygonB.length; k++) {
if (lineIntersectsLine(polygon, multiPolygonB[k])) return true;
}
}

return false;
}

function multiPolygonIntersectsBufferedMultiLine(multiPolygon, multiLine, radius) {
for (var i = 0; i < multiLine.length; i++) {
var line = multiLine[i];

for (var j = 0; j < multiPolygon.length; j++) {
var polygon = multiPolygon[j];

if (polygon.length >= 3) {
for (var k = 0; k < line.length; k++) {
if (polygonContainsPoint(polygon, line[k])) return true;
}
}

if (lineIntersectsBufferedLine(polygon, line, radius)) return true;
}
}
return false;
}

function lineIntersectsBufferedLine(lineA, lineB, radius) {

if (lineA.length > 1) {
if (lineIntersectsLine(lineA, lineB)) return true;

// Check whether any point in either line is within radius of the other line
for (var j = 0; j < lineB.length; j++) {
if (pointIntersectsBufferedLine(lineB[j], lineA, radius)) return true;
}
}

for (var k = 0; k < lineA.length; k++) {
if (pointIntersectsBufferedLine(lineA[k], lineB, radius)) return true;
}

return false;
}

function lineIntersectsLine(lineA, lineB) {
for (var i = 0; i < lineA.length - 1; i++) {
var a0 = lineA[i];
var a1 = lineA[i + 1];
for (var j = 0; j < lineB.length - 1; j++) {
var b0 = lineB[j];
var b1 = lineB[j + 1];
if (lineSegmentIntersectsLineSegment(a0, a1, b0, b1)) return true;
}
}
return false;
}


// http://bryceboe.com/2006/10/23/line-segment-intersection-algorithm/
function isCounterClockwise(a, b, c) {
return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x);
}

function lineSegmentIntersectsLineSegment(a0, a1, b0, b1) {
return isCounterClockwise(a0, b0, b1) !== isCounterClockwise(a1, b0, b1) &&
isCounterClockwise(a0, a1, b0) !== isCounterClockwise(a0, a1, b1);
}

function pointIntersectsBufferedLine(p, line, radius) {
var radiusSquared = radius * radius;

if (line.length === 1) return p.distSqr(line[0]) < radiusSquared;

for (var i = 1; i < line.length; i++) {
// Find line segments that have a distance <= radius^2 to p
// In that case, we treat the line as "containing point p".
var v = line[i - 1], w = line[i];
if (distToSegmentSquared(p, v, w) < radiusSquared) return true;
}
return false;
}

// Code from http://stackoverflow.com/a/1501725/331379.
function distToSegmentSquared(p, v, w) {
var l2 = v.distSqr(w);
if (l2 === 0) return p.distSqr(v);
var t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
if (t < 0) return p.distSqr(v);
if (t > 1) return p.distSqr(w);
return p.distSqr(w.sub(v)._mult(t)._add(v));
}

// point in polygon ray casting algorithm
function multiPolygonContainsPoint(rings, p) {
var c = false,
ring, p1, p2;

for (var k = 0; k < rings.length; k++) {
ring = rings[k];
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
p1 = ring[i];
p2 = ring[j];
if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
c = !c;
}
}
}
return c;
}

function polygonContainsPoint(ring, p) {
var c = false;
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
var p1 = ring[i];
var p2 = ring[j];
if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
c = !c;
}
}
return c;
}
4 changes: 2 additions & 2 deletions js/source/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ exports._queryRenderedVectorFeatures = function(queryGeometry, params, classes,
var renderedFeatureLayers = [];
for (var r = 0; r < tilesIn.length; r++) {
var tileIn = tilesIn[r];
if (!tileIn.tile.featureTree) continue;
if (!tileIn.tile.featureIndex) continue;

renderedFeatureLayers.push(tileIn.tile.featureTree.query({
renderedFeatureLayers.push(tileIn.tile.featureIndex.query({
queryGeometry: tileIn.queryGeometry,
scale: tileIn.scale,
tileSize: tileIn.tile.tileSize,
Expand Down
6 changes: 3 additions & 3 deletions js/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

var util = require('../util/util');
var Bucket = require('../data/bucket');
var FeatureTree = require('../data/feature_tree');
var FeatureIndex = require('../data/feature_index');
var vt = require('vector-tile');
var Protobuf = require('pbf');
var GeoJSONFeature = require('../util/vectortile_to_geojson');
Expand Down Expand Up @@ -84,7 +84,7 @@ Tile.prototype = {

this.collisionBoxArray = new CollisionBoxArray(data.collisionBoxArray);
this.collisionTile = new CollisionTile(data.collisionTile, this.collisionBoxArray);
this.featureTree = new FeatureTree(data.featureTree, data.rawTileData, this.collisionTile);
this.featureIndex = new FeatureIndex(data.featureIndex, data.rawTileData, this.collisionTile);
this.rawTileData = data.rawTileData;
this.buckets = unserializeBuckets(data.buckets);
},
Expand All @@ -101,7 +101,7 @@ Tile.prototype = {
if (this.isUnloaded) return;

this.collisionTile = new CollisionTile(data.collisionTile, this.collisionBoxArray);
this.featureTree.setCollisionTile(this.collisionTile);
this.featureIndex.setCollisionTile(this.collisionTile);

// Destroy and delete existing symbol buckets
for (var id in this.buckets) {
Expand Down
16 changes: 8 additions & 8 deletions js/source/worker_tile.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

var FeatureTree = require('../data/feature_tree');
var FeatureIndex = require('../data/feature_index');
var CollisionTile = require('../symbol/collision_tile');
var Bucket = require('../data/bucket');
var CollisionBoxArray = require('../symbol/collision_box');
Expand All @@ -27,7 +27,7 @@ WorkerTile.prototype.parse = function(data, layers, actor, rawTileData, callback

this.collisionBoxArray = new CollisionBoxArray();
var collisionTile = new CollisionTile(this.angle, this.pitch, this.collisionBoxArray);
var featureTree = new FeatureTree(this.coord, this.overscaling, collisionTile, data.layers);
var featureIndex = new FeatureIndex(this.coord, this.overscaling, collisionTile, data.layers);
var sourceLayerCoder = new DictionaryCoder(data.layers ? Object.keys(data.layers).sort() : ['_geojsonTileLayer']);

var stats = { _total: 0 };
Expand Down Expand Up @@ -105,13 +105,13 @@ WorkerTile.prototype.parse = function(data, layers, actor, rawTileData, callback
symbolBuckets = this.symbolBuckets = [],
otherBuckets = [];

featureTree.bucketLayerIDs = {};
featureIndex.bucketLayerIDs = {};

for (var id in bucketsById) {
bucket = bucketsById[id];
if (bucket.features.length === 0) continue;

featureTree.bucketLayerIDs[bucket.index] = bucket.layerIDs;
featureIndex.bucketLayerIDs[bucket.index] = bucket.layerIDs;

buckets.push(bucket);

Expand Down Expand Up @@ -183,7 +183,7 @@ WorkerTile.prototype.parse = function(data, layers, actor, rawTileData, callback
if (bucket.type !== 'symbol') {
for (var i = 0; i < bucket.features.length; i++) {
var feature = bucket.features[i];
featureTree.insert(feature, feature.index, bucket.sourceLayerIndex, bucket.index);
featureIndex.insert(feature, feature.index, bucket.sourceLayerIndex, bucket.index);
}
}

Expand All @@ -201,17 +201,17 @@ WorkerTile.prototype.parse = function(data, layers, actor, rawTileData, callback
tile.redoPlacementAfterDone = false;
}

var featureTree_ = featureTree.serialize();
var featureIndex_ = featureIndex.serialize();
var collisionTile_ = collisionTile.serialize();
var collisionBoxArray = tile.collisionBoxArray.serialize();
var transferables = [rawTileData].concat(featureTree_.transferables).concat(collisionTile_.transferables);
var transferables = [rawTileData].concat(featureIndex_.transferables).concat(collisionTile_.transferables);

var nonEmptyBuckets = buckets.filter(isBucketEmpty);

callback(null, {
buckets: nonEmptyBuckets.map(serializeBucket),
bucketStats: stats, // TODO put this in a separate message?
featureTree: featureTree_.data,
featureIndex: featureIndex_.data,
collisionTile: collisionTile_.data,
collisionBoxArray: collisionBoxArray,
rawTileData: rawTileData
Expand Down
Loading

0 comments on commit 6e8bcef

Please sign in to comment.