Dynamically adding and removing segments from a track in OpenLayers 3 - javascript

I want to display a track in real-time with OpenLayers 3 which disolves at the end, just like a snails trail.
Only appending new coordinates to a LineString is easy. See this example. But removing coordinates from the end of the line does not seem to be supported by the API.
How should I go about that? Is extending the LineString class the only option? Or should I use a separate feature for each line segment?
Update:
I used this code with ol-debug.js. But get/setFlatCoordinates is not exported in the compiled version.
var flatCoordinates = geometry.getFlatCoordinates(); // not exported
if (flatCoordinates && flatCoordinates.length > 100) {
// remove first coordinate elements from array
flatCoordinates.splice(0, geometry.getStride());
// call push with coordinate elements as arguments
Array.prototype.push.apply(flatCoordinates, coordinate);
// update coordinates calling change()
geometry.setFlatCoordinates(geometry.getLayout(), flatCoordinates);
}
else {
geometry.appendCoordinate(coordinate);
}

The appendCoordinate method is a shortcut for the fairly common use case of adding a coordinate to the end of a LineString. To modify geometries with more control, set your desired coordinates with setCoordinates.
var maxCoords = 100;
var coords = lineString.getCoordinates(); // get coordinate array
coords.unshift(newCoord); // add to beginning of array
if (coords.length > maxCoords) {
coords.length = maxCoords;
}
lineString.setCoordinates(coords);

Related

Vector Tile Points on top of Polygons can't be clicked on

I have made my own vector tile .pbf data, consisting of polygons, lines and points.
The source of .pbf data are from a grouplayer of shapefiles in Geoserver, where each shapefile has been ordered so that points are at the top:
Using OpenLayers, I've styled the vector tiles and followed the example for "Vector Tile Info". The difference is mine is toggled by single click.
The problem is that I can't click on any point, even as a sprite image, if it is completely within a polygon.
It's possible to click on lines that are completely within a polygon.
Underlying polygons are selected as feature[0] when attempting to select a point.
What might be the cause of this render order? That a point is only a single pixel?
What might be the remedy?
I'm very new to JavaScript and OpenLayers so I'm hoping it's something naive.
I need to be able to prioritize the points when they're surrounded by a polygon.
The order of feature returned by map.getFeaturesAtPixel() seems to be random and not related to render order. Either check all the features returned, not just entry [0], or sort the array so Points/MultiPoints are lower in the sort order than LineStrings/MultiLineStrings with Polygons last
function rank(feature) {
var type = feature.getType();
if (type.indexOf('Point') > -1) {
return 1;
} else if (type.indexOf('LineString') > -1) {
return 2;
} else {
return 3;
}
}
map.getFeaturesAtPixel(event.pixel).sort(function(a, b) {
return rank(a) - rank(b);
});

How can you use mapbox-gl-Draw to create rectangle of 8 points?

I have a unique issue to using Mapbox-gl-Draw. Mapbox Draw allows you to create a multi point polygon easily, however I need it to use only 8 points as an algorithm i use elsewhere accepts 8 lnglat elements. Im wondering if it is possible that when the draw button is clicked, that it displays a rectangle of 8 points on screen with the ability to drag each of the points?
Below is the expected outcome with one click of draw.
Currently i can draw a rectangle but with only 4 points, each corner of the rectangle.
Rather than trying to mess with mapbox-gl-draw, you should just take the output, then add the midpoints programmatically. You can use turf's midpoint function for that purpose. Something like:
const coords = rectangle.geometry.coordinates[0];
const newcoords = [];
coords.forEach((point, i) => {
newcoords.push(point);
if (i < coords.length - 1) {
newcoords.push(midpoint(point, coords[i+1]).geometry.coords);
}
});
rectangle.geometry.coordinates = newcoords;

How to determine two polygons is same?

I have a polygon, it has five points like this:
then I add another point to polygon (the red one):
what's the algorithm to determine two polygons is same one (not just angle/length is same, coordinates also same too).
As your same means same shape,size,orientation and position
then it is really simple
you have 2 polygons defined as set of points
A={ a0,a1...a(n-1) } and B={ b0,b1,...b(m-1) }
for starters I assume you have no oversampling (line is always 2 points not more)
compare m,n
if not equal shapes are different so stop
otherwise m==n so I will use just n from now on
find (a(i)==b(j)) where i,j=<0,n)
this is needed in case the polygons are not starting from the same point
otherwise i=0,j=0
for complicated (self intersecting) shapes you need to find unique points
(not duplicates, or the same count of duplicates with the same next point)
otherwise just set i=0 and find j with single O(n) loop
if no common point found stop (not the same polygons)
compare the points
for (k=0;k<n;k++)
{
if (a(i)!=b(j)) return false; // not the same
i++; if (i>=n) i=0;
j++; if (j>=n) j=0;
} return true; // are the same
the point comparison can be done like this if (|a(i)-b(j)|>=max_difference_treshold)
no need to compare sqrt-ed distances the treshold can be powered by 2 instead
I usually use something like 1e-6 or 1e-10 values
For oversampled polygon you need to resample points of booth A,B first
take 3 neighboring points p(i-1),p(i),p(i+1)
compute dx,dy between 2 pairs
d1=p(i)-p(i-1); dx1=p1.x; dy1=p1.y;
d2=p(i+1)-p(i); dx2=p2.x; dy2=p2.y;
if (dx1*dy2==dx1*dy1) then delete p(i) from the set
you should handle the zero cases (any dx,dy is zero) separately prior to this
//import turf library
var turf = require('#turf/turf');
// create polygon using turf or you can directly use geoJSON
var polygon1 = turf.polygon(firstPolygonCoordinates);
var polygon2 = turf.polygon(secondPolygonCoordinates);
// Compare two polygon using turf's booleanEqual method
if (turf.booleanEqual(polygon1, polygon2)) {
// Add your logic here
}
Depending on your point of view. Two rectangles may be the same independently of the position.
If the position is a requirement you will need to compare the vertex
coordinates.
If the position is NOT a requirement, you should compare the
rectangle size. In this case, you need to obtain the distance between
the vertex. In this case, my advice is you should use the rectangle
base and height for comparing.

How to plot animated line chart using d3 while the data table is required to be updated every 1 second?

I want to simulate the situation where there is continuous incoming real time (dynamic) data and feed to the chart
Therefore, I try to demonstrate an animated line chart using d3 while the data array is required to be continuous updated every 1 second.
The original work is inspired by benjchristensen
And here is my modified html source: my source code
I try to populate the buffer[100] with random number for every 1 second by calling function startGenerator()
function startGenerator()
{
//signalGenerator();
setInterval("signalGenerator()", 1000); //
}
function signalGenerator()
{
var buffer = new Array(100);
var i;
for (i = 0; i < buffer.length; i++)
{
buffer[i] = Math.random() * 10;
}
FeedDataToChart("#graph1", 300, 300, "basis", true, 100, 100, buffer, 0);
}
Below is the FeedDataToChart() parameter names:
function FeedDataToChart(id, width, height, interpolation, animate, updateDelay, transitionDelay, data, startIndex)
I use a counter to check for the re-draw data index, every time when re-draw new data, counter increase by 1. Until the the counter < data.length-1, the re-draw timer should stop and get new data from the buffer[100] again.
function stopTimer()
{
clearInterval(myTimer);
alert("The redraw timer stop here, get new data from buffer");
}
function startTimer()
{
if (myTimer == null)
{
myTimer = setInterval(function() {
if (counter < data.length - 1)
{
var v = data.shift(); // remove the first element of the array
data.push(v); // add a new element to the array (we're just taking the number we just shifted off the front and appending to the end)
if(animate)
{
redrawWithAnimation();
}
else
{
redrawWithoutAnimation();
}
counter++;
}
else
{
alert("no more data in buffer");
stopTimer();
counter = startIndex;
}
}, updateDelay);
}
}
My problem occurs when I tried to repeat the startGenerator() function, the results shows like this:
I am pretty new to javascript. Can anyone pin point me how to pull data from the buffer every 1 second and update the single line chart continuously?
Thank you!
EDIT I update the source and problem has been minimized: My New Source
I add the .remove() line at the stopTimer() to remove the sgv-holded data and reset the global buffer to null and call the startGenerator() function again to pull new data when timer stop.
Now the only problem I having is that everytime when new graph is created, it create a new sgv path where below the previous one. The new graph is moving downwards everytime when it was created. Check the new source I updated today. You will catch my description after you run the code.
How can I fixed the sgv path at the same location everytime when I update it?
The problem with simply calling FeedDataToChart again is that it creates entirely new svg and path elements rather than reusing the existing ones. The inner redraw method you're using follows the standard D3 update pattern, so take that as an example for how to rework the update.
Start by separating initialization from drawing. There's no reason the scale and line generators need to change on every update, so get that out of the way early. Also create the svg element as soon as you can and don't change it unless you really need to. Likewise with the path element.
The general structure of any D3 visualization will consist of three distinct phases:
intialization - perform as soon as possible after script load
create scale and svg generator functions (scale, axis, line, area etc.)
create data cleanup & aggregation functions (nest, custom filters etc.)
creation - perform as soon as possible after DOM ready
create svg element (only ever call .append("svg") once per chart)
create chart and axis groups (see the margin conventions)
draw - perform when data is available
first segment by enter/update/remove selections
find existing data elements (.selectAll() on data element CSS class)
rebind data to chart
perform on enter selection
create chart data elements (only call .append(el) after .enter())
set chart data element CSS class (make this the canonical handle)
set static properties (but if you can put these in CSS all the better)
perform on enter and update selections
set dynamic properties (here is the call to .attr("d", line))
you may also have a remove portion (for exit selections)
For more information take a look at Mike Bostock's tutorial on selections in D3.
The create, update, remove process is actually pretty simple once you get used to it. Here's a prototypical example:
// select existing data elements and rebind data
var dots = d3.selectAll(".dot")
.data(data);
// append a circle for new dots
dots.enter().append("circle")
.attr("class", "dot")
.attr("r", 4);
// remove old dots
dots.exit().remove();
// update all new and existing dots
dots.attr("cx", x)
.attr("cy", y);
The key to writing simple and performant D3 is to minimize the changes you're making to the DOM, since DOM manipulation tends to be the major performance bottleneck.

OpenLayers ModifyFeature not saving new vertices

I'm just getting started with OpenLayers, and have hit a small snag - when I create a LineString and then try to modify it, I can move the existing vertices and drag the virtual vertices to create new ones. When I continue to add to the line though, only the changes to the existing vertices are saved - new vertices are discarded. Am I missing something? You can see an example of what I'm talking about here:
http://dev.darrenhall.info/temp/open-layers/modify-feature/
Click to add points, and use the dots to edit, then click to continue adding to see what I mean. Any help would be appreciated! Thanks!
Darren
After a quick look, your code looks more complex than it should be.
You manually push point into an array of point manually on click, and generate a linestring with those points.
You don’t listen to any change done with virtual vertices. I don’t get why, in your addWayPoint function, you don’t get the geometry of the feature from the layer rather than your array of point.
Maybe that would be a good start to use the real feature geometry and avoid using your route.waypoints.
In the end I decided not to use modifyFeature, and instead went for using vectors as handles and manually handling the dragging and line modification. You can see my workaround here:
http://dev.darrenhall.info/temp/open-layers/draw-route
The guys at Ordnance Survey cam up with a (rather simple) fix for my code though that repopulates the array from the vertices after modification:
function addWayPoint(e) {
var position = osMap.getLonLatFromViewPortPx(e.xy);
if(route.waypoints.length>1) {
layers.lines.layer.removeFeatures([layers.lines.feature]);
}
/* vvvvvvvvvvv start */
/* Get the potentially modified feature */
if (modifyFeature.feature) {
route.waypoints = [];
var vertices = modifyFeature.feature.geometry.getVertices();
for (i = 0; i < vertices.length; i++) {
//console.log(vertices[i]);
route.waypoints.push(vertices[i]);
}
}
/* ^^^^^^^^^^^ end */
route.waypoints.push(new OpenLayers.Geometry.Point(position.lon, position.lat));
var string = new OpenLayers.Geometry.LineString(route.waypoints);
layers.lines.feature = new OpenLayers.Feature.Vector(string, null, styles.pink);
layers.lines.feature.attributes['id']=1;
layers.lines.layer.addFeatures([layers.lines.feature]);
for (i = 0; i < layers.lines.layer.features.length; i++) {
if (layers.lines.layer.features[i].attributes.id == 1) {
modifyFeature.selectFeature(layers.lines.layer.features[i]);
}
}
}

Categories

Resources