Merge branch 'master' into vector-api

This commit is contained in:
Tom Payne
2013-11-28 21:00:18 +01:00
17 changed files with 280 additions and 1117 deletions

View File

@@ -4,6 +4,7 @@ goog.require('goog.asserts');
goog.require('ol.Coordinate');
goog.require('ol.Feature');
goog.require('ol.FeatureRenderIntent');
goog.require('ol.Map');
goog.require('ol.MapBrowserEvent');
goog.require('ol.MapBrowserEvent.EventType');
@@ -16,7 +17,6 @@ goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Interaction');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorLayerRenderIntent');
goog.require('ol.source.Vector');
@@ -113,7 +113,7 @@ ol.interaction.Draw.prototype.setMap = function(map) {
if (!goog.isNull(map)) {
if (goog.isNull(this.sketchLayer_)) {
var layer = new ol.layer.Vector({
source: new ol.source.Vector({parser: null}),
source: new ol.source.Vector(),
style: this.layer_.getStyle()
});
layer.setTemporary(true);
@@ -233,7 +233,7 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) {
var start = event.getCoordinate();
this.finishCoordinate_ = start;
var sketchFeature = new ol.Feature();
sketchFeature.setRenderIntent(ol.layer.VectorLayerRenderIntent.SELECTED);
sketchFeature.setRenderIntent(ol.FeatureRenderIntent.SELECTED);
var features = [sketchFeature];
var geometry;
if (this.mode_ === ol.interaction.DrawMode.POINT) {
@@ -242,7 +242,7 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) {
var sketchPoint = new ol.Feature({
geom: new ol.geom.Point(start.slice())
});
sketchPoint.setRenderIntent(ol.layer.VectorLayerRenderIntent.TEMPORARY);
sketchPoint.setRenderIntent(ol.FeatureRenderIntent.TEMPORARY);
this.sketchPoint_ = sketchPoint;
features.push(sketchPoint);
@@ -256,7 +256,7 @@ ol.interaction.Draw.prototype.startDrawing_ = function(event) {
sketchFeature.setGeometry(geometry);
this.sketchFeature_ = sketchFeature;
this.sketchLayer_.addFeatures(features);
this.sketchLayer_.getVectorSource().addFeatures(features);
};
@@ -325,7 +325,7 @@ ol.interaction.Draw.prototype.addToDrawing_ = function(event) {
ol.interaction.Draw.prototype.finishDrawing_ = function(event) {
var sketchFeature = this.abortDrawing_();
goog.asserts.assert(!goog.isNull(sketchFeature));
sketchFeature.setRenderIntent(ol.layer.VectorLayerRenderIntent.DEFAULT);
sketchFeature.setRenderIntent(ol.FeatureRenderIntent.DEFAULT);
var geometry = sketchFeature.getGeometry();
var coordinates = geometry.getCoordinates();
if (this.mode_ === ol.interaction.DrawMode.LINESTRING) {
@@ -344,7 +344,7 @@ ol.interaction.Draw.prototype.finishDrawing_ = function(event) {
} else if (this.type_ === ol.geom.GeometryType.MULTIPOLYGON) {
sketchFeature.setGeometry(new ol.geom.MultiPolygon([coordinates]));
}
this.layer_.addFeatures([sketchFeature]);
this.layer_.getVectorSource().addFeatures([sketchFeature]);
};
@@ -363,7 +363,7 @@ ol.interaction.Draw.prototype.abortDrawing_ = function() {
features.push(this.sketchPoint_);
this.sketchPoint_ = null;
}
this.sketchLayer_.removeFeatures(features);
this.sketchLayer_.getVectorSource().removeFeatures(features);
}
return sketchFeature;
};

View File

@@ -6,6 +6,7 @@ goog.require('goog.events');
goog.require('goog.functions');
goog.require('ol.CollectionEventType');
goog.require('ol.Feature');
goog.require('ol.FeatureRenderIntent');
goog.require('ol.MapBrowserEvent.EventType');
goog.require('ol.ViewHint');
goog.require('ol.coordinate');
@@ -18,9 +19,8 @@ goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Drag');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorEventType');
goog.require('ol.layer.VectorLayerRenderIntent');
goog.require('ol.source.Vector');
goog.require('ol.source.VectorEventType');
goog.require('ol.structs.RBush');
@@ -59,6 +59,13 @@ ol.interaction.Modify = function(opt_options) {
*/
this.layerFilter_ = layerFilter;
/**
* Layer lookup. Keys source id to layer.
* @type {Object.<number, ol.layer.Vector>}
* @private
*/
this.layerLookup_ = null;
/**
* Temporary sketch layer.
* @type {ol.layer.Vector}
@@ -121,12 +128,13 @@ ol.interaction.Modify.prototype.setMap = function(map) {
}
if (!goog.isNull(map)) {
this.layerLookup_ = {};
if (goog.isNull(this.rBush_)) {
this.rBush_ = new ol.structs.RBush();
}
if (goog.isNull(this.sketchLayer_)) {
var sketchLayer = new ol.layer.Vector({
source: new ol.source.Vector({parser: null})
source: new ol.source.Vector()
});
this.sketchLayer_ = sketchLayer;
sketchLayer.setTemporary(true);
@@ -141,6 +149,7 @@ ol.interaction.Modify.prototype.setMap = function(map) {
false, this);
} else {
// removing from a map, clean up
this.layerLookup_ = null;
this.rBush_ = null;
this.sketchLayer_ = null;
}
@@ -168,9 +177,11 @@ ol.interaction.Modify.prototype.handleLayerAdded_ = function(evt) {
ol.interaction.Modify.prototype.addLayer_ = function(layer) {
if (this.layerFilter_(layer) && layer instanceof ol.layer.Vector &&
!layer.getTemporary()) {
this.addIndex_(layer.getFeatures(ol.layer.Vector.selectedFeaturesFilter),
var source = layer.getVectorSource();
this.layerLookup_[goog.getUid(source)] = layer;
this.addIndex_(source.getFeatures(ol.layer.Vector.selectedFeaturesFilter),
layer);
goog.events.listen(layer, ol.layer.VectorEventType.INTENTCHANGE,
goog.events.listen(source, ol.source.VectorEventType.INTENTCHANGE,
this.handleIntentChange_, false, this);
}
};
@@ -195,9 +206,11 @@ ol.interaction.Modify.prototype.handleLayerRemoved_ = function(evt) {
ol.interaction.Modify.prototype.removeLayer_ = function(layer) {
if (this.layerFilter_(layer) && layer instanceof ol.layer.Vector &&
!layer.getTemporary()) {
var source = layer.getVectorSource();
delete this.layerLookup_[goog.getUid(source)];
this.removeIndex_(
layer.getFeatures(ol.layer.Vector.selectedFeaturesFilter));
goog.events.unlisten(layer, ol.layer.VectorEventType.INTENTCHANGE,
source.getFeatures(ol.layer.Vector.selectedFeaturesFilter));
goog.events.unlisten(source, ol.source.VectorEventType.INTENTCHANGE,
this.handleIntentChange_, false, this);
}
};
@@ -248,17 +261,19 @@ ol.interaction.Modify.prototype.removeIndex_ = function(features) {
/**
* Listen for feature additions.
* @param {ol.layer.VectorEvent} evt Event object.
* @param {ol.source.VectorEvent} evt Event object.
* @private
*/
ol.interaction.Modify.prototype.handleIntentChange_ = function(evt) {
var layer = evt.target;
var source = evt.target;
goog.asserts.assertInstanceof(source, ol.source.Vector);
var layer = this.layerLookup_[goog.getUid(source)];
goog.asserts.assertInstanceof(layer, ol.layer.Vector);
var features = evt.features;
for (var i = 0, ii = features.length; i < ii; ++i) {
var feature = features[i];
var renderIntent = feature.getRenderIntent();
if (renderIntent == ol.layer.VectorLayerRenderIntent.SELECTED) {
if (renderIntent == ol.FeatureRenderIntent.SELECTED) {
this.addIndex_([feature], layer);
} else {
this.removeIndex_([feature]);
@@ -322,7 +337,7 @@ ol.interaction.Modify.prototype.createOrUpdateVertexFeature_ =
if (goog.isNull(vertexFeature)) {
vertexFeature = new ol.Feature({g: new ol.geom.Point(coordinates)});
this.vertexFeature_ = vertexFeature;
this.sketchLayer_.addFeatures([vertexFeature]);
this.sketchLayer_.getVectorSource().addFeatures([vertexFeature]);
} else {
var geometry = vertexFeature.getGeometry();
geometry.setCoordinates(coordinates);
@@ -341,7 +356,7 @@ ol.interaction.Modify.prototype.handleDragStart = function(evt) {
this.dragSegments_ = [];
var vertexFeature = this.vertexFeature_;
if (!goog.isNull(vertexFeature) && vertexFeature.getRenderIntent() !=
ol.layer.VectorLayerRenderIntent.HIDDEN) {
ol.FeatureRenderIntent.HIDDEN) {
var renderIntent = vertexFeature.getRenderIntent();
var insertVertices = [];
var vertex = vertexFeature.getGeometry().getCoordinates();
@@ -360,7 +375,7 @@ ol.interaction.Modify.prototype.handleDragStart = function(evt) {
original.setSymbolizers(feature.getSymbolizers());
feature.setOriginal(original);
}
if (renderIntent == ol.layer.VectorLayerRenderIntent.TEMPORARY) {
if (renderIntent == ol.FeatureRenderIntent.TEMPORARY) {
if (ol.coordinate.equals(segment[0], vertex)) {
dragSegments.push([node, 0]);
} else if (ol.coordinate.equals(segment[1], vertex)) {
@@ -455,7 +470,7 @@ ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) {
var vertexFeature = this.vertexFeature_;
var rBush = this.rBush_;
var nodes = rBush.getAllInExtent(box);
var renderIntent = ol.layer.VectorLayerRenderIntent.HIDDEN;
var renderIntent = ol.FeatureRenderIntent.HIDDEN;
if (nodes.length > 0) {
nodes.sort(sortByDistance);
var node = nodes[0];
@@ -469,10 +484,10 @@ ol.interaction.Modify.prototype.handleMouseMove_ = function(evt) {
var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
renderIntent = ol.layer.VectorLayerRenderIntent.FUTURE;
renderIntent = ol.FeatureRenderIntent.FUTURE;
if (dist <= 10) {
vertex = squaredDist1 > squaredDist2 ? segment[1] : segment[0];
renderIntent = ol.layer.VectorLayerRenderIntent.TEMPORARY;
renderIntent = ol.FeatureRenderIntent.TEMPORARY;
}
vertexFeature = this.createOrUpdateVertexFeature_(node.style, vertex);
this.modifiable_ = true;

View File

@@ -3,11 +3,11 @@ goog.provide('ol.interaction.Select');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('ol.Feature');
goog.require('ol.FeatureRenderIntent');
goog.require('ol.events.ConditionType');
goog.require('ol.events.condition');
goog.require('ol.interaction.Interaction');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorLayerRenderIntent');
@@ -97,21 +97,21 @@ ol.interaction.Select.prototype.select =
}
var featuresToSelect = featuresByLayer[i];
var selectedFeatures = layer.getFeatures(
var selectedFeatures = layer.getVectorSource().getFeatures(
ol.layer.Vector.selectedFeaturesFilter);
if (clear) {
for (var j = selectedFeatures.length - 1; j >= 0; --j) {
selectedFeatures[j].setRenderIntent(
ol.layer.VectorLayerRenderIntent.DEFAULT);
ol.FeatureRenderIntent.DEFAULT);
}
}
for (var j = featuresToSelect.length - 1; j >= 0; --j) {
var feature = featuresToSelect[j];
// TODO: Make toggle configurable
feature.setRenderIntent(feature.getRenderIntent() ==
ol.layer.VectorLayerRenderIntent.SELECTED ?
ol.layer.VectorLayerRenderIntent.DEFAULT :
ol.layer.VectorLayerRenderIntent.SELECTED);
ol.FeatureRenderIntent.SELECTED ?
ol.FeatureRenderIntent.DEFAULT :
ol.FeatureRenderIntent.SELECTED);
}
// TODO: Dispatch an event with selectedFeatures and unselectedFeatures
}

View File

@@ -1,13 +0,0 @@
goog.provide('ol.layer.VectorLayerRenderIntent');
/**
* @enum {string}
*/
ol.layer.VectorLayerRenderIntent = {
DEFAULT: 'default',
FUTURE: 'future',
HIDDEN: 'hidden',
SELECTED: 'selected',
TEMPORARY: 'temporary'
};

View File

@@ -75,7 +75,7 @@ ol.parser.KML = function(opt_options) {
'*': function(node, obj) {
if (this.extractAttributes === true) {
var len = node.childNodes.length;
if ((len === 1 || len === 2) && (node.firstChild.nodeType === 3 ||
if (len > 0 && (node.firstChild.nodeType === 3 ||
node.firstChild.nodeType === 4)) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_attribute'].apply(this, arguments);

View File

@@ -8,6 +8,7 @@ goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.vec.Mat4');
goog.require('ol.Feature');
goog.require('ol.FeatureRenderIntent');
goog.require('ol.geom.AbstractCollection');
goog.require('ol.geom.Geometry');
goog.require('ol.geom.GeometryType');
@@ -17,7 +18,6 @@ goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.layer.VectorLayerRenderIntent');
goog.require('ol.style.IconLiteral');
goog.require('ol.style.LineLiteral');
goog.require('ol.style.Literal');
@@ -158,7 +158,7 @@ ol.renderer.canvas.Vector.prototype.renderLineStringFeatures_ =
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) {
if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) {
continue;
}
id = goog.getUid(feature);
@@ -247,7 +247,7 @@ ol.renderer.canvas.Vector.prototype.renderPointFeatures_ =
context.globalAlpha = alpha;
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) {
if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) {
continue;
}
id = goog.getUid(feature);
@@ -323,7 +323,7 @@ ol.renderer.canvas.Vector.prototype.renderText_ =
for (var i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) {
if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) {
continue;
}
vecs = ol.renderer.canvas.Vector.getLabelVectors(
@@ -391,7 +391,7 @@ ol.renderer.canvas.Vector.prototype.renderPolygonFeatures_ =
context.beginPath();
for (i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (feature.getRenderIntent() == ol.layer.VectorLayerRenderIntent.HIDDEN) {
if (feature.getRenderIntent() == ol.FeatureRenderIntent.HIDDEN) {
continue;
}
geometry = feature.getGeometry();

View File

@@ -1,630 +0,0 @@
// rtree.js - General-Purpose Non-Recursive Javascript R-Tree Library
// Version 0.6.2, December 5st 2009
//
// Copyright (c) 2009 Jon-Carlos Rivera
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Jon-Carlos Rivera - imbcmdth@hotmail.com
goog.provide('ol.structs.RTree');
goog.require('goog.array');
goog.require('ol.extent');
/**
* @typedef {{extent: ol.Extent,
* leaf: (Object|undefined),
* nodes: (Array.<ol.structs.RTreeNode>|undefined),
* target: (Object|undefined)}}
*/
ol.structs.RTreeNode;
/**
* @constructor
* @param {number=} opt_maxWidth Width before a node is split. Default is `6`.
*/
ol.structs.RTree = function(opt_maxWidth) {
/**
* Maximum width of any node before a split.
* @private
* @type {number}
*/
this.maxWidth_ = goog.isDef(opt_maxWidth) ? opt_maxWidth : 6;
/**
* Minimum width of any node before a merge.
* @private
* @type {number}
*/
this.minWidth_ = Math.floor(this.maxWidth_ / 2);
/**
* Start with an empty root-tree.
* @private
* @type {ol.structs.RTreeNode}
*/
this.rootTree_ = /** @type {ol.structs.RTreeNode} */
({extent: ol.extent.createEmpty(), nodes: []});
};
/**
* @param {ol.structs.RTreeNode} node Node.
* @private
*/
ol.structs.RTree.recalculateExtent_ = function(node) {
var n = node.nodes.length;
var extent = node.extent;
if (n === 0) {
ol.extent.empty(extent);
} else {
var firstNodeExtent = node.nodes[0].extent;
extent[0] = firstNodeExtent[0];
extent[2] = firstNodeExtent[2];
extent[1] = firstNodeExtent[1];
extent[3] = firstNodeExtent[3];
var i;
for (i = 1; i < n; ++i) {
ol.extent.extend(extent, node.nodes[i].extent);
}
}
};
/**
* This is Jon-Carlos Rivera's special addition to the world of r-trees.
* Every other (simple) method he found produced poor trees.
* This skews insertions to prefering squarer and emptier nodes.
*
* @param {number} l L.
* @param {number} w W.
* @param {number} fill Fill.
* @private
* @return {number} Squarified ratio.
*/
ol.structs.RTree.squarifiedRatio_ = function(l, w, fill) {
// Area of new enlarged rectangle
var peri = (l + w) / 2; // Average size of a side of the new rectangle
var area = l * w; // Area of new rectangle
// return the ratio of the perimeter to the area - the closer to 1 we are,
// the more "square" a rectangle is. conversly, when approaching zero the
// more elongated a rectangle is
var geo = area / (peri * peri);
return area * fill / geo;
};
/**
* Choose the best for rectangle to be inserted into.
*
* @param {ol.structs.RTreeNode} rect Rectangle.
* @param {ol.structs.RTreeNode} root Root to start search.
* @private
* @return {Array} Leaf node parent.
*/
ol.structs.RTree.prototype.chooseLeafSubtree_ = function(rect, root) {
var bestChoiceIndex = -1;
var bestChoiceStack = [];
var bestChoiceArea;
bestChoiceStack.push(root);
var nodes = root.nodes;
do {
if (bestChoiceIndex != -1) {
bestChoiceStack.push(nodes[bestChoiceIndex]);
nodes = nodes[bestChoiceIndex].nodes;
bestChoiceIndex = -1;
}
for (var i = nodes.length - 1; i >= 0; --i) {
var lTree = nodes[i];
if (goog.isDef(lTree.leaf)) {
// Bail out of everything and start inserting
bestChoiceIndex = -1;
break;
}
// Area of new enlarged rectangle
var oldLRatio = ol.structs.RTree.squarifiedRatio_(
lTree.extent[2] - lTree.extent[0],
lTree.extent[3] - lTree.extent[1],
lTree.nodes.length + 1);
// Enlarge rectangle to fit new rectangle
var nw = (lTree.extent[2] > rect.extent[2] ?
lTree.extent[2] : rect.extent[2]) -
(lTree.extent[0] < rect.extent[0] ?
lTree.extent[0] : rect.extent[0]);
var nh = (lTree.extent[3] > rect.extent[3] ?
lTree.extent[3] : rect.extent[3]) -
(lTree.extent[1] < rect.extent[1] ?
lTree.extent[1] : rect.extent[1]);
// Area of new enlarged rectangle
var lRatio = ol.structs.RTree.squarifiedRatio_(
nw, nh, lTree.nodes.length + 2);
if (bestChoiceIndex < 0 ||
Math.abs(lRatio - oldLRatio) < bestChoiceArea) {
bestChoiceArea = Math.abs(lRatio - oldLRatio);
bestChoiceIndex = i;
}
}
} while (bestChoiceIndex != -1);
return bestChoiceStack;
};
/**
* Non-recursive insert function.
*
* @param {ol.Extent} extent Extent.
* @param {Object} obj Object to insert.
*/
ol.structs.RTree.prototype.insert = function(extent, obj) {
var node = /** @type {ol.structs.RTreeNode} */
({extent: extent, leaf: obj});
this.insertSubtree_(node, this.rootTree_);
};
/**
* Non-recursive internal insert function.
*
* @param {ol.structs.RTreeNode} node Node to insert.
* @param {ol.structs.RTreeNode} root Root to begin insertion at.
* @private
*/
ol.structs.RTree.prototype.insertSubtree_ = function(node, root) {
var bc; // Best Current node
// Initial insertion is special because we resize the Tree and we don't
// care about any overflow (seriously, how can the first object overflow?)
if (root.nodes.length === 0) {
root.extent = ol.extent.clone(node.extent);
root.nodes.push(node);
return;
}
// Find the best fitting leaf node
// chooseLeaf returns an array of all tree levels (including root)
// that were traversed while trying to find the leaf
var treeStack = this.chooseLeafSubtree_(node, root);
var workingObject = node;
// Walk back up the tree resizing and inserting as needed
do {
//handle the case of an empty node (from a split)
if (bc && goog.isDef(bc.nodes) && bc.nodes.length === 0) {
var pbc = bc; // Past bc
bc = treeStack.pop();
for (var t = 0, tt = bc.nodes.length; t < tt; ++t) {
if (bc.nodes[t] === pbc || bc.nodes[t].nodes.length === 0) {
bc.nodes.splice(t, 1);
break;
}
}
} else {
bc = treeStack.pop();
}
// If there is data attached to this workingObject
var isArray = goog.isArray(workingObject);
if (goog.isDef(workingObject.leaf) ||
goog.isDef(workingObject.nodes) || isArray) {
// Do Insert
if (isArray) {
for (var ai = 0, aii = workingObject.length; ai < aii; ++ai) {
ol.extent.extend(bc.extent, workingObject[ai].extent);
}
bc.nodes = bc.nodes.concat(workingObject);
} else {
ol.extent.extend(bc.extent, workingObject.extent);
bc.nodes.push(workingObject); // Do Insert
}
if (bc.nodes.length <= this.maxWidth_) { // Start Resizeing Up the Tree
workingObject = {extent: ol.extent.clone(bc.extent)};
} else { // Otherwise Split this Node
// linearSplit_() returns an array containing two new nodes
// formed from the split of the previous node's overflow
var a = this.linearSplit_(bc.nodes);
workingObject = a;//[1];
if (treeStack.length < 1) { // If are splitting the root..
bc.nodes.push(a[0]);
treeStack.push(bc); // Reconsider the root element
workingObject = a[1];
}
}
} else { // Otherwise Do Resize
//Just keep applying the new bounding rectangle to the parents..
ol.extent.extend(bc.extent, workingObject.extent);
workingObject = ({extent: ol.extent.clone(bc.extent)});
}
} while (treeStack.length > 0);
};
/**
* Split a set of nodes into two roughly equally-filled nodes.
*
* @param {Array.<ol.structs.RTreeNode>} nodes Array of nodes.
* @private
* @return {Array.<ol.structs.RTreeNode>} An array of two nodes.
*/
ol.structs.RTree.prototype.linearSplit_ = function(nodes) {
var n = this.pickLinear_(nodes);
while (nodes.length > 0) {
this.pickNext_(nodes, n[0], n[1]);
}
return n;
};
/**
* Pick the "best" two starter nodes to use as seeds using the "linear"
* criteria.
*
* @param {Array.<ol.structs.RTreeNode>} nodes Array of source nodes.
* @private
* @return {Array.<ol.structs.RTreeNode>} An array of two nodes.
*/
ol.structs.RTree.prototype.pickLinear_ = function(nodes) {
var lowestHighX = nodes.length - 1;
var highestLowX = 0;
var lowestHighY = nodes.length - 1;
var highestLowY = 0;
var t1, t2;
for (var i = nodes.length - 2; i >= 0; --i) {
var l = nodes[i];
if (l.extent[0] > nodes[highestLowX].extent[0]) {
highestLowX = i;
} else if (l.extent[2] < nodes[lowestHighX].extent[1]) {
lowestHighX = i;
}
if (l.extent[1] > nodes[highestLowY].extent[1]) {
highestLowY = i;
} else if (l.extent[3] < nodes[lowestHighY].extent[3]) {
lowestHighY = i;
}
}
var dx = Math.abs(nodes[lowestHighX].extent[2] -
nodes[highestLowX].extent[0]);
var dy = Math.abs(nodes[lowestHighY].extent[3] -
nodes[highestLowY].extent[1]);
if (dx > dy) {
if (lowestHighX > highestLowX) {
t1 = nodes.splice(lowestHighX, 1)[0];
t2 = nodes.splice(highestLowX, 1)[0];
} else {
t2 = nodes.splice(highestLowX, 1)[0];
t1 = nodes.splice(lowestHighX, 1)[0];
}
} else {
if (lowestHighY > highestLowY) {
t1 = nodes.splice(lowestHighY, 1)[0];
t2 = nodes.splice(highestLowY, 1)[0];
} else {
t2 = nodes.splice(highestLowY, 1)[0];
t1 = nodes.splice(lowestHighY, 1)[0];
}
}
return [
/** @type {ol.structs.RTreeNode} */
({extent: ol.extent.clone(t1.extent), nodes: [t1]}),
/** @type {ol.structs.RTreeNode} */
({extent: ol.extent.clone(t2.extent), nodes: [t2]})
];
};
/**
* Insert the best source rectangle into the best fitting parent node: a or b.
*
* @param {Array.<ol.structs.RTreeNode>} nodes Source node array.
* @param {ol.structs.RTreeNode} a Target node array a.
* @param {ol.structs.RTreeNode} b Target node array b.
* @private
*/
ol.structs.RTree.prototype.pickNext_ = function(nodes, a, b) {
// Area of new enlarged rectangle
var areaA = ol.structs.RTree.squarifiedRatio_(a.extent[2] - a.extent[0],
a.extent[3] - a.extent[1], a.nodes.length + 1);
var areaB = ol.structs.RTree.squarifiedRatio_(b.extent[2] - b.extent[0],
b.extent[3] - b.extent[1], b.nodes.length + 1);
var highAreaDelta;
var highAreaNode;
var lowestGrowthGroup;
for (var i = nodes.length - 1; i >= 0; --i) {
var l = nodes[i];
var newAreaA = [
a.extent[0] < l.extent[0] ? a.extent[0] : l.extent[0],
a.extent[2] > l.extent[2] ? a.extent[2] : l.extent[2],
a.extent[1] < l.extent[1] ? a.extent[1] : l.extent[1],
a.extent[3] > l.extent[3] ? a.extent[3] : l.extent[3]
];
var changeNewAreaA = Math.abs(ol.structs.RTree.squarifiedRatio_(
newAreaA[1] - newAreaA[0],
newAreaA[3] - newAreaA[2], a.nodes.length + 2) - areaA);
var newAreaB = [
b.extent[0] < l.extent[0] ? b.extent[0] : l.extent[0],
b.extent[2] > l.extent[2] ? b.extent[2] : l.extent[2],
b.extent[1] < l.extent[1] ? b.extent[1] : l.extent[1],
b.extent[3] > l.extent[3] ? b.extent[3] : l.extent[3]
];
var changeNewAreaB = Math.abs(ol.structs.RTree.squarifiedRatio_(
newAreaB[1] - newAreaB[0], newAreaB[3] - newAreaB[2],
b.nodes.length + 2) - areaB);
var changeNewAreaDelta = Math.abs(changeNewAreaB - changeNewAreaA);
if (!highAreaNode || !highAreaDelta ||
changeNewAreaDelta < highAreaDelta) {
highAreaNode = i;
highAreaDelta = changeNewAreaDelta;
lowestGrowthGroup = changeNewAreaB < changeNewAreaA ? b : a;
}
}
var tempNode = nodes.splice(highAreaNode, 1)[0];
if (a.nodes.length + nodes.length + 1 <= this.minWidth_) {
a.nodes.push(tempNode);
ol.extent.extend(a.extent, tempNode.extent);
} else if (b.nodes.length + nodes.length + 1 <= this.minWidth_) {
b.nodes.push(tempNode);
ol.extent.extend(b.extent, tempNode.extent);
}
else {
lowestGrowthGroup.nodes.push(tempNode);
ol.extent.extend(lowestGrowthGroup.extent, tempNode.extent);
}
};
/**
* Non-recursive function that deletes a specific region.
*
* @param {ol.Extent} extent Extent.
* @param {Object=} opt_obj Object.
* @return {Array} Result.
* @this {ol.structs.RTree}
*/
ol.structs.RTree.prototype.remove = function(extent, opt_obj) {
arguments[0] = /** @type {ol.structs.RTreeNode} */ ({extent: extent});
switch (arguments.length) {
case 1:
arguments[1] = false; // opt_obj == false for conditionals
case 2:
arguments[2] = this.rootTree_; // Add root node to end of argument list
default:
arguments.length = 3;
}
if (arguments[1] === false) { // Do area-wide †
var numberDeleted = 0;
var result = [];
do {
numberDeleted = result.length;
result = result.concat(this.removeSubtree_.apply(this, arguments));
} while (numberDeleted != result.length);
return result;
} else { // Delete a specific item
return this.removeSubtree_.apply(this, arguments);
}
};
/**
* Find the best specific node(s) for object to be deleted from.
*
* @param {ol.structs.RTreeNode} rect Rectangle.
* @param {Object} obj Object.
* @param {ol.structs.RTreeNode} root Root to start search.
* @private
* @return {Array} Leaf node parent.
*/
ol.structs.RTree.prototype.removeSubtree_ = function(rect, obj, root) {
var hitStack = []; // Contains the elements that overlap
var countStack = []; // Contains the elements that overlap
var returnArray = [];
var currentDepth = 1;
if (!rect || !ol.extent.intersects(rect.extent, root.extent)) {
return returnArray;
}
/** @type {ol.structs.RTreeNode} */
var workingObject = /** @type {ol.structs.RTreeNode} */
({extent: ol.extent.clone(rect.extent), target: obj});
countStack.push(root.nodes.length);
hitStack.push(root);
do {
var tree = hitStack.pop();
var i = countStack.pop() - 1;
if (goog.isDef(workingObject.target)) {
// We are searching for a target
while (i >= 0) {
var lTree = tree.nodes[i];
if (ol.extent.intersects(workingObject.extent, lTree.extent)) {
if ((workingObject.target && goog.isDef(lTree.leaf) &&
lTree.leaf === workingObject.target) ||
(!workingObject.target && (goog.isDef(lTree.leaf) ||
ol.extent.containsExtent(workingObject.extent, lTree.extent))))
{ // A Match !!
// Yup we found a match...
// we can cancel search and start walking up the list
if (goog.isDef(lTree.nodes)) {
// If we are deleting a node not a leaf...
returnArray = this.searchSubtree_(lTree, true, [], lTree);
tree.nodes.splice(i, 1);
} else {
returnArray = tree.nodes.splice(i, 1);
}
// Resize MBR down...
ol.structs.RTree.recalculateExtent_(tree);
workingObject.target = undefined;
if (tree.nodes.length < this.minWidth_) { // Underflow
workingObject.nodes = /** @type {Array} */
(this.searchSubtree_(tree, true, [], tree));
}
break;
} else if (goog.isDef(lTree.nodes)) {
// Not a Leaf
currentDepth += 1;
countStack.push(i);
hitStack.push(tree);
tree = lTree;
i = lTree.nodes.length;
}
}
i -= 1;
}
} else if (goog.isDef(workingObject.nodes)) {
// We are unsplitting
tree.nodes.splice(i + 1, 1); // Remove unsplit node
// workingObject.nodes contains a list of elements removed from the
// tree so far
if (tree.nodes.length > 0) {
ol.structs.RTree.recalculateExtent_(tree);
}
for (var t = 0, tt = workingObject.nodes.length; t < tt; ++t) {
this.insertSubtree_(workingObject.nodes[t], tree);
}
workingObject.nodes.length = 0;
if (hitStack.length === 0 && tree.nodes.length <= 1) {
// Underflow..on root!
workingObject.nodes = /** @type {Array} */
(this.searchSubtree_(tree, true, workingObject.nodes, tree));
tree.nodes.length = 0;
hitStack.push(tree);
countStack.push(1);
} else if (hitStack.length > 0 && tree.nodes.length < this.minWidth_) {
// Underflow..AGAIN!
workingObject.nodes = /** @type {Array} */
(this.searchSubtree_(tree, true, workingObject.nodes, tree));
tree.nodes.length = 0;
} else {
workingObject.nodes = undefined; // Just start resizing
}
} else { // we are just resizing
ol.structs.RTree.recalculateExtent_(tree);
}
currentDepth -= 1;
} while (hitStack.length > 0);
return returnArray;
};
/**
* Non-recursive search function
*
* @param {ol.Extent} extent Extent.
* @return {Array} Result.
* @this {ol.structs.RTree}
*/
ol.structs.RTree.prototype.search = function(extent) {
var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent});
return /** @type {Array} */ (
this.searchSubtree_(rect, false, [], this.rootTree_));
};
/**
* Non-recursive search function
*
* @param {ol.Extent} extent Extent.
* @return {Object} Result. Keys are UIDs of the values.
* @this {ol.structs.RTree}
*/
ol.structs.RTree.prototype.searchReturningObject = function(extent) {
var rect = /** @type {ol.structs.RTreeNode} */ ({extent: extent});
return /** @type {Object} */ (
this.searchSubtree_(rect, false, [], this.rootTree_, true));
};
/**
* Non-recursive internal search function
*
* @param {ol.structs.RTreeNode} rect Rectangle.
* @param {boolean} returnNode Do we return nodes?
* @param {Array|Object} result Result.
* @param {ol.structs.RTreeNode} root Root.
* @param {boolean=} opt_resultAsObject If set, result will be an object keyed
* by UID.
* @private
* @return {Array|Object} Result.
*/
ol.structs.RTree.prototype.searchSubtree_ = function(
rect, returnNode, result, root, opt_resultAsObject) {
var resultObject = {};
var hitStack = []; // Contains the elements that overlap
if (!ol.extent.intersects(rect.extent, root.extent)) {
return result;
}
hitStack.push(root.nodes);
do {
var nodes = hitStack.pop();
for (var i = nodes.length - 1; i >= 0; --i) {
var lTree = nodes[i];
if (ol.extent.intersects(rect.extent, lTree.extent)) {
if (goog.isDef(lTree.nodes)) { // Not a Leaf
hitStack.push(lTree.nodes);
} else if (goog.isDef(lTree.leaf)) { // A Leaf !!
if (!returnNode) {
// TODO keep track of type on all nodes so we don't have to
// walk all the way in to the leaf to know that we don't need it
var obj = lTree.leaf;
if (goog.isDef(opt_resultAsObject)) {
resultObject[goog.getUid(obj).toString()] = obj;
} else {
result.push(obj);
}
} else {
result.push(lTree);
}
}
}
}
} while (hitStack.length > 0);
if (goog.isDef(opt_resultAsObject)) {
return resultObject;
} else {
return result;
}
};