Merge pull request #1759 from twpayne/interpolate-m

Interpolate-M
This commit is contained in:
Tom Payne
2014-02-28 17:35:46 +01:00
6 changed files with 193 additions and 0 deletions

View File

@@ -36,6 +36,7 @@
<div id="docs">
<p>See the <a href="igc.js" target="_blank">igc.js source</a> to see how this is done.</p>
</div>
<input id="time" type="range" value="0" steps="1"></input>
<div id="tags">complex-geometry, closest-feature, igc, opencyclemap</div>
</div>
<div class="span4 offset4">

View File

@@ -1,4 +1,6 @@
goog.require('ol.Attribution');
goog.require('ol.Feature');
goog.require('ol.FeatureOverlay');
goog.require('ol.Map');
goog.require('ol.View2D');
goog.require('ol.geom.LineString');
@@ -8,6 +10,7 @@ goog.require('ol.layer.Vector');
goog.require('ol.source.IGC');
goog.require('ol.source.OSM');
goog.require('ol.style.Circle');
goog.require('ol.style.Fill');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
@@ -47,6 +50,19 @@ var vectorSource = new ol.source.IGC({
]
});
var time = {
start: Infinity,
stop: -Infinity,
duration: 0
};
vectorSource.on('addfeature', function(event) {
var geometry = event.feature.getGeometry();
time.start = Math.min(time.start, geometry.getFirstCoordinate()[2]);
time.stop = Math.max(time.stop, geometry.getLastCoordinate()[2]);
time.duration = time.stop - time.start;
});
var map = new ol.Map({
layers: [
new ol.layer.Tile({
@@ -137,3 +153,34 @@ map.on('postcompose', function(evt) {
vectorContext.drawLineStringGeometry(line);
}
});
var featureOverlay = new ol.FeatureOverlay({
map: map,
style: new ol.style.Style({
image: new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({
color: 'rgba(255,0,0,0.9)'
}),
stroke: null
})
})
});
$('#time').on('input', function(event) {
var value = parseInt($(this).val(), 10) / 100;
var m = time.start + (time.duration * value);
vectorSource.forEachFeature(function(feature) {
var geometry = /** @type {ol.geom.LineString} */ (feature.getGeometry());
var coordinate = geometry.getCoordinateAtM(m, true);
var highlight = feature.get('highlight');
if (highlight == undefined) {
highlight = new ol.Feature(new ol.geom.Point(coordinate));
feature.set('highlight', highlight);
featureOverlay.addFeature(highlight);
} else {
highlight.getGeometry().setCoordinates(coordinate);
}
});
map.render();
});

View File

@@ -305,6 +305,75 @@ ol.geom.flat.lineStringInterpolate =
};
/**
* @param {Array.<number>} flatCoordinates Flat coordinates.
* @param {number} offset Offset.
* @param {number} end End.
* @param {number} stride Stride.
* @param {ol.geom.GeometryLayout} layout Layout.
* @param {number} m M.
* @param {boolean} extrapolate Extrapolate.
* @return {ol.Coordinate} Coordinate.
*/
ol.geom.flat.lineStringCoordinateAtM =
function(flatCoordinates, offset, end, stride, layout, m, extrapolate) {
if ((layout != ol.geom.GeometryLayout.XYM &&
layout != ol.geom.GeometryLayout.XYZM) ||
end == offset) {
return null;
}
var coordinate;
if (m < flatCoordinates[offset + stride - 1]) {
if (extrapolate) {
coordinate = flatCoordinates.slice(offset, offset + stride);
coordinate[stride - 1] = m;
return coordinate;
} else {
return null;
}
} else if (flatCoordinates[end - 1] < m) {
if (extrapolate) {
coordinate = flatCoordinates.slice(end - stride, end);
coordinate[stride - 1] = m;
return coordinate;
} else {
return null;
}
}
// FIXME use O(1) search
if (m == flatCoordinates[offset + stride - 1]) {
return flatCoordinates.slice(offset, offset + stride);
}
var lo = offset / stride;
var hi = end / stride;
while (lo < hi) {
var mid = (lo + hi) >> 1;
if (m < flatCoordinates[(mid + 1) * stride - 1]) {
hi = mid;
} else {
lo = mid + 1;
}
}
var m0 = flatCoordinates[lo * stride - 1];
if (m == m0) {
return flatCoordinates.slice((lo - 1) * stride, (lo - 1) * stride + stride);
}
var m1 = flatCoordinates[(lo + 1) * stride - 1];
goog.asserts.assert(m0 < m);
goog.asserts.assert(m <= m1);
var t = (m - m0) / (m1 - m0);
coordinate = [];
var i;
for (i = 0; i < stride - 1; ++i) {
coordinate.push((1 - t) * flatCoordinates[(lo - 1) * stride + i] +
t * flatCoordinates[lo * stride + i]);
}
coordinate.push(m);
goog.asserts.assert(coordinate.length == stride);
return coordinate;
};
/**
* @param {Array.<number>} flatCoordinates Flat coordinates.
* @param {number} offset Offset.

View File

@@ -1,5 +1,6 @@
@exportSymbol ol.geom.LineString
@exportProperty ol.geom.LineString.prototype.clone
@exportProperty ol.geom.LineString.prototype.getCoordinateAtM
@exportProperty ol.geom.LineString.prototype.getCoordinates
@exportProperty ol.geom.LineString.prototype.getLength
@exportProperty ol.geom.LineString.prototype.getType

View File

@@ -80,6 +80,18 @@ ol.geom.LineString.prototype.closestPointXY =
};
/**
* @param {number} m M.
* @param {boolean=} opt_extrapolate Extrapolate.
* @return {ol.Coordinate} Coordinate.
*/
ol.geom.LineString.prototype.getCoordinateAtM = function(m, opt_extrapolate) {
var extrapolate = goog.isDef(opt_extrapolate) ? opt_extrapolate : false;
return ol.geom.flat.lineStringCoordinateAtM(this.flatCoordinates, 0,
this.flatCoordinates.length, this.stride, this.layout, m, extrapolate);
};
/**
* @return {ol.geom.RawLineString} Coordinates.
* @todo stability experimental

View File

@@ -228,6 +228,69 @@ describe('ol.geom.LineString', function() {
});
describe('with a simple XYM coordinates', function() {
var lineString;
beforeEach(function() {
lineString = new ol.geom.LineString(
[[1, 2, 3], [4, 5, 6]], ol.geom.GeometryLayout.XYM);
});
describe('#getCoordinateAtM', function() {
it('returns the expected value', function() {
expect(lineString.getCoordinateAtM(2, false)).to.be(null);
expect(lineString.getCoordinateAtM(2, true)).to.eql([1, 2, 2]);
expect(lineString.getCoordinateAtM(3, false)).to.eql([1, 2, 3]);
expect(lineString.getCoordinateAtM(3, true)).to.eql([1, 2, 3]);
expect(lineString.getCoordinateAtM(4, false)).to.eql([2, 3, 4]);
expect(lineString.getCoordinateAtM(4, true)).to.eql([2, 3, 4]);
expect(lineString.getCoordinateAtM(5, false)).to.eql([3, 4, 5]);
expect(lineString.getCoordinateAtM(5, true)).to.eql([3, 4, 5]);
expect(lineString.getCoordinateAtM(6, false)).to.eql([4, 5, 6]);
expect(lineString.getCoordinateAtM(6, true)).to.eql([4, 5, 6]);
expect(lineString.getCoordinateAtM(7, false)).to.eql(null);
expect(lineString.getCoordinateAtM(7, true)).to.eql([4, 5, 7]);
});
});
});
describe('with several XYZM coordinates', function() {
var lineString;
beforeEach(function() {
lineString = new ol.geom.LineString([
[0, 0, 0, 0],
[1, -1, 2, 1],
[2, -2, 4, 2],
[4, -4, 8, 4],
[8, -8, 16, 8],
[12, -12, 24, 12],
[14, -14, 28, 14],
[15, -15, 30, 15],
[16, -16, 32, 16],
[18, -18, 36, 18],
[22, -22, 44, 22]
]);
});
describe('#getCoordinateAtM', function() {
it('returns the expected value', function() {
expect(lineString.getLayout()).to.be(ol.geom.GeometryLayout.XYZM);
var m;
for (m = 0; m <= 22; m += 0.5) {
expect(lineString.getCoordinateAtM(m, true)).to.eql(
[m, -m, 2 * m, m]);
}
});
});
});
});