What I am trying to do is to replace or change the geometry of a feature after it has been drawn. e.g.: I draw a line and after it has been drawn I modify the geometry with Turf.js, to make a buffer around the drawn line and display the buffered line(polygon) instead of the line.
var draw = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString'
});
draw.on('drawend', function(e) {
//Sending the LineString to a Turf function with a buffer value and getting a Polygon in return
var bfc = bufferedFeatureCollection(100, e.feature);
//Replacing the LineString geometry with a Polygon geometry
e.feature.setGeometry(bfc[0].getGeometry());
//Removes and terminates the draw action....
map.removeInteraction(this);
});
Now from console.log(), I can see that the feature.geometry has been changed from ol.geom.linestring to ol.geom.polygon. But on the map I still see a line being displayed.
What am I doing wrong?
Do these modifications after the feature is added to ol.source.Vector, so:
vectorSource.on('addfeature', function(evt){
//Sending the LineString to a Turf function with a buffer value and getting a Polygon in return
var bfc = bufferedFeatureCollection(100, evt.feature);
//Replacing the LineString geometry with a Polygon geometry
evt.feature.setGeometry(bfc[0].getGeometry());
//Removes and terminates the draw action....
map.removeInteraction(draw);
});
The mistake I made was that the buffer value was too low to be seen as a buffer hence I only saw a line(even do it was a polygon) I had to up the buffer value from 100 to 10000000...
So in both draw.on('drawend') and vectorSource.on('addfeature') it is possible to replace the geometry that has been drawn.
Edit:
For the completeness of the answer. The reason I had to change the buffer from 100 to 10000000 is that I forgot to transform the geometry into EPSG:4326, before passing it to the Turf function. Turf only works with EPSG:4326.
var draw = new ol.interaction.Draw({
source: vectorSource,
type: 'LineString'
});
draw.on('drawend', function(e) {
e.feature.getGeometry().transform('EPSG:3857', 'EPSG:4326');
var bfc = bufferedFeatureCollection(radius, e.feature);
bfc[0].getGeometry().transform('EPSG:4326', 'EPSG:3857');
e.feature.setGeometry(bfc[0].getGeometry());
//Removes and terminates the draw action....
map.removeInteraction(this);
});
Related
I'm using d3-geo package to plot points and shapes in latitude and longitude space. I would like to plot a simple polygon, triangle, square, star, etc centered on a point location. I would also, if possible, like to plot text at a given [lat,lon].
I currently have the below code working and plotting a ring at the given coords [-40.5, 65.5]. I would, however, like to be able to define different shapes at this location, is there an easy way of doing this without manually defining the shape myself? There is an empty 'properties' field that I'm unable to find any documentation on that could be used? D3-geo documentation and google searches have yielded zilch so far.
let geoGenerator = geoPath()
.projection(projection)
.pointRadius(4)
.context(context); //2d Canvas contect
context.beginPath();
geoGenerator({
type: "Feature",
geometry: {
type: "Point",
coordinates: [-40.5, 65.5]
},
properties: {}
});
context.stroke();
Geojson does not have any property in its specification that specifies the type of symbol (symbol shape, color, size, etc) that should be drawn. Geojson only specifies the geometry (point, line, polygon, etc) of the drawn object in geographic coordinates.
Of course you can use the properties property of a geojson feature to hold symbol data, it just has no effect on the rendering the feature unless you build that functionality yourself.
While geojson doesn't have any specifications for symbology, the geoPath generator in D3 let's you specify one part of a drawn symbol: the radius of a point (as points are dimensionless otherwise). However, other than this, d3-geo doesn't offer any support for drawing specific symbols, it can only project geometry.
To draw a symbol at a specific geographic coordinate, you'll want project the coordinate (projection([longitude,latitude])). Now you have a coordinate in pixel values, you can use that coordinate to draw your symbol. You don't want to try and draw the symbol in geographic coordinates as this isn't scalable and it is dependent on projection.
Here's a simple implementation with d3-symbol (I haven't drawn the rest of the world, just two points, but they are projected properly):
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = d3.symbol()
.type(d3.symbolWye)
.context(context)
.size(200);
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
context.save();
// position symbol:
context.translate(...xy);
// Draw symbol:
context.beginPath();
shape();
context.fill();
// Remove translation:
context.restore();
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could specify your own shapes too, here's a simple square implementation:
var context = d3.select("canvas").node().getContext("2d");
var points = [[-136,63],[-123,50]];
var projection = d3.geoMercator();
var shape = function(xy) {
var offset = 10;
var x = xy[0];
var y = xy[1];
context.beginPath();
// draw a sqaure:
context.moveTo(x-offset,y-offset);
context.lineTo(x-offset,y+offset);
context.lineTo(x+offset,y+offset);
context.lineTo(x+offset,y-offset);
context.lineTo(x-offset,y-offset);
context.fill();
context.strokeStyle = "steelblue";
context.lineWidth = 5;
context.lineCap = "square"
context.stroke();
}
var shapey = function(lonlat) {
// Get xy Data
var xy = projection(lonlat);
// save without translation.
shape(xy);
}
points.forEach(shapey);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<canvas width="500" height="300"></canvas>
Of course you could get a lot fancier in these basic functions, but for the purposes of demonstration they should be sufficient.
I'm trying to set the pivot point of a group that contains a raster image to the center of the screen to no avail.
Please see my current code here: condesandbox example
Any help will be much appreciated
Based on your code example, I guess that you just have to set the position of your raster to be the view center and its pivot point will automatically be the same by default.
Here is a sketch demonstrating a possible solution.
// Create the raster.
const raster = new Raster({
source: 'http://assets.paperjs.org/images/marilyn.jpg',
// When image is loaded...
onLoad: () => {
// ...place it at center.
raster.position = view.center;
}
});
// Include raster in a group.
const group = new Group(raster);
// Mark center with a circle.
new Path.Circle({
center: view.center,
radius: 10,
fillColor: 'blue'
});
// Scale the group (the pivot point is bounds center by default).
group.scale(0.5);
The examples shown in here show how to move an object along the path in Paperjs but how do I rotate them correctly along the path?
In the examples shown on the link above, people suggested by using a circle as an example. But once changed to a rectangle new Path.Rectangle(new Point(20,20), new Size(20,20)); you can see that it moves along the path but does not actually rotate in the direction of the path.
How do I calculate the rotation and set it to my object?
In order to calculate the rotation, you need to know the tangent vector to the path at the position of your rectangle.
This can be retrieved with path.getTangentAt(offset) method.
Then, an easy way to animate the rotation of an item is to set item.applyMatrix to false and then animate the item.rotation property on each frame.
Here is a sketch demonstrating the solution.
// Create the rectangle to animate along the path.
// Note that matrix is not applied, this will allow us to easily animate its
// rotation.
var rectangle = new Path.Rectangle({
point: view.center,
size: new Size(100, 200),
strokeColor: 'orange',
applyMatrix: false
});
// Create the path along which the rectangle will be animated.
var path = new Path.Circle({
center: view.center,
radius: 250,
strokeColor: 'blue'
});
// On each frame...
function onFrame(event) {
// ...calculate the time of the animation between 0 and 1...
var slowness = 400;
var time = event.count % slowness / slowness;
// ...and move the rectangle.
updateRectangle(time);
}
function updateRectangle(time) {
// Calculate the offset relatively to the path length.
var offset = time * path.length;
// Get point to position the rectangle.
var point = path.getPointAt(offset);
// Get tangent vector at this point.
var tangent = path.getTangentAt(offset);
// Move rectangle.
rectangle.position = point;
// Rotate rectangle.
rectangle.rotation = tangent.angle;
}
Background
Specs
OpenLayers 4.4.1
OSM
I'm fairly new to OpenLayers and have never used vectors before (primarily because I found out that I was using OpenLayers version 1, and had to relearn everything).
My application adds circles to a map relating to a position with a specific radius indicating position accuracy.
In its operation, multiple circles are added to the map at different times.
This is my code for loading the map:
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: 'mapdiv',
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}),
view: new ol.View({
//center: [0, 0],
zoom: 16
})
});
//this is where all map 'features' (circles) are stored
var vectorSource = new ol.source.Vector({
projection: 'EPSG:4326'
});
As you can see, I load the 'vector source' right after the map as I understood that it holds all 'vectors' which are displayed on the map so long as you specify it as the 'source'.
This is the code I use to generate the circle (source) (I tweaked it at getPointResolution because the OP made a mistake):
//code from https://stackoverflow.com/a/28299599
function addCircle(map, vectorSource, radius) {
var view = map.getView();
var projection = view.getProjection();
var resolutionAtEquator = view.getResolution();
var center = view.getCenter();
var pointResolution = ol.proj.getPointResolution(projection, resolutionAtEquator, center);
var resolutionFactor = resolutionAtEquator/pointResolution;
var radius = (radius / ol.proj.METERS_PER_UNIT.m) * resolutionFactor;
var circle = new ol.geom.Circle(center, radius);
var circleFeature = new ol.Feature(circle);
// vector layer
vectorSource.addFeature(circleFeature);
var vectorLayer = new ol.layer.Vector({
source: vectorSource
});
map.addLayer(vectorLayer);
}
Problem
Loading one circle goes normally, adds a blue stroked, opaque circle at the specified location with specified radius.
Loading a second circle appears more opaque than the last. Moving the map to the previous circle, it is also more opaque.
With each added circle, the apparent opacity increases for all displayed circles.
Running vectorLayer.getOpacity() in every circle generation results in 1, when clearly the circle is translucent, becoming increasingly opaque with every new circle.
Summary
Looking around, it appears that often it is the case that the developer is reloading the same circle over and over until many are stacked on top of one another. It almost seems like this is the case for me too, except I've triple-checked that I'm only running addCircle() once and the circle is in a different position than the last.
Is it possible that OpenLayers is redrawing all previous circles with every new circle?
Maybe this isn't related to getOpacity but has to do with the color as an rgba() combination...
Question
I want every circle to remain the same after drawing new circles. The default opacity and color is fine.
Am I doing something wrong?
Here's a fiddle as an example - https://jsfiddle.net/f5zrLt20/5/
Define the layer when defining the vectorSource:
var layer = null;
//this is where all map 'features' (circles) are stored
var vectorSource = new ol.source.Vector({
projection: 'EPSG:4326'
});
And check if it exists on creating a new circle:
// If layer is not yet set, create new layer and add it to map
if (!layer) {
vectorSource.addFeature(circleFeature);
layer = new ol.layer.Vector({
source: vectorSource
});
map.addLayer(layer);
}
//Otherwise, just add feature to the source
else {
layer.getSource().addFeature(circleFeature);
}
I have a project which consist in visualizing the exchange of data between points on a map.
I'm using Leaflet to draw polylines from coordinates in a GeoJson file and Leaflet.polylineDecorator (https://github.com/bbecquet/Leaflet.PolylineDecorator) to put an animated arrow on the polyline.
The thing is that I need to visualize the stream in both directions. I started by adding to my Geojson file polylines in the other direction but the issue is when I zoom out, the two polylines are stacked.
So I found Leaflet.polylineOffset (https://github.com/bbecquet/Leaflet.PolylineOffset) which allows to create an another polyline just by setting the offset option.
I thought, i just had to do the same to put the animated arrow on it but when i'm doing it, the animation is affected to the original polyline. In fact, the offset polyline keeps the coordinates from the original one.
I wanted to know if there is a way to apply this animation to the offset polyline.
Here is my code:
d3.json("data/trajetsFibreDCSigma.json",function (data){ // getting polylines' data from a json file to add them on the map
L.geoJson(data, {
style: function(feature){return {color : feature.properties.stroke,opacity: 1};}, // setting the style of the polylines
onEachFeature: function(feature){
// getting the coordinates of the polyline from the json file
var latlng = feature.geometry.coordinates;
var size = feature.geometry.coordinates.length;
var buffer;
// reversing the order of latitude and longitude in the array because a L.latLng object needs the latitude first and I have the opposite in my json file
for (i=0;i<size;i++)
{
buffer = latlng[i][0];
latlng[i][0] = latlng[i][1];
latlng[i][1] = buffer;
}
var polylineOffset = L.polyline(latlng,{offset: 5,color: 'blue',opacity: 1}).addTo(map); // putting an offset to the polyline
addArrow(latlng,feature);
addArrow(polylineOffset,feature);
}
}).addTo(map);
});
function addArrow(polyline,feature){ // function to add an arrow on the map
var arrowHead = L.polylineDecorator(polyline).addTo(map); // creating an arrow which will be put on the polyline
var arrowOffset = 0;
window.setInterval(function() { // creating an animation for the arrow to cross the polyline
arrowHead.setPatterns([
{offset: arrowOffset+'%', repeat: 0, symbol: L.Symbol.arrowHead({pixelSize: 10, polygon: false,
pathOptions: {stroke: true,color: feature.properties.stroke,opacity: 1}})}
]);
if(++arrowOffset > 100)
arrowOffset = 0;
}, 100);
}
(If I'm just calling addArrow with the offset polyline, it will pop on the original one).
I found a solution to get the offset polyline's coordinates.
The PolylineOffset plugin has a function which returns the offset coordinates.
You can use it like this:
var pts = L.PolylineOffset.offsetLatLngs(latlng,10,map); // getting the coordinates from the offset polyline
where latlng is the array of the original coordinates
; 10 is the offset
; map is your leaflet map