this.drawInteraction = new ol.interaction.Draw({
features: this.sourceFeatures,
type: 'Polygon',
geometryFunction: (coords, geom) => this.onDrawGeometryFunction(coords, geom)
});
private onDrawGeometryFunction(coords, geom): void {
if (!geom) {
geom = new ol.geom.Polygon(null);
}
if (coords[0].length !== this.coords_length) {
//if intersects undo last point
if (this.intersectionsExistInPolygon(coords[0])) {
this.drawInteraction.removeLastPoint();
return geom;
}
this.coords_length = coords[0].length;
}
geom.setCoordinates(coords);
return geom;
}
I can able to identify the intersection until I click on the starting point to complete the draw(by connecting through the drawn line). Is there a way to listen the draw complete(but not the 'drawend') before it happen? to keep the draw mode active by removing the intersecting last point.
Well, I was using Openlayers#v3.14.2 this finishcondition is available from v3.16.0. So I had to update only the version it worked fine..
Related
With Leaflet.Markercluster, how is it possible to check if a marker is in a cluster?
Use Leaflet's hasLayer() function
Regular visible markers technically exist as layers in the map. Leaflet also has a function hasLayer() that returns true/false whether a given reference, like a stored marker, currently exists (ie "is visible") in the map as a layer.
For my specific problem, I gave a special visual decoration to a selected map marker (e.g. red border), and this decoration remained even after that marker went in-and-out of a cluster. I needed the selected marker to deselect itself if it entered a cluster.
Below is rough logic that allows me to check whether a given marker reference is visible to the user after a cluster event (that may occur for any reason, like zoom events or non-user driven movements in the map).
Relevant abbreviated code:
I hold my clustering like this...
this.markersClustered = L.markerClusterGroup({ // Lots of stuff here... } })
In my click event for individual markers, I store the marker in a "selectedItem" object (which I use for all kinds of other logic in my app)...
onEachFeature(feature, marker) {
marker.on("click", e => {
// ... (lots of code) ...
// Save instance of the marker; I call it "layer" only
// because I only use this for the hasLayer() check
this.selectedItem.layer = marker
// Here do other stuff too, whatever you need...
//this.selectedItem.target = e.target
//this.selectedItem.latlng = e.latlng
//this.selectedItem.id = e.target.feature.id
//this.selectedItem.icon = layer._icon
// ... (lots of code) ...
})
}
When cluster animations end, using hasLayer(), check whether the selected marker is in a cluster, and do stuff.
Note: the check is done at the END of clustering animation in my use case, because I didn't want the user to see the selected marker lose it's special visual decoration while the clustering animation was happening (it would appear buggy).
this.markersClustered.on('animationend', (e) => {
// Check visibility of selected marker
if(this.map.hasLayer(this.selectedItem.layer)) {
// True: Not in a cluster (ie marker is visible on map)
} else {
// False: In a cluster (ie marker is not visible on map)
}
console.log("Selected marker visible?:" + this.map.hasLayer(this.selectedItem.layer))
})
I'm trying to draw few polygons that dynamically change their position.
The problem is that by using:
onUpdatePolygonData(data){
/// DO SOME CALCULATIONS TO GET point1, point2 point3...
if(Cesium.defined(entity.polygon)
entity.polygon.hierarchy = new Cesium.PolygonHierarchy([point1, point2, point3])
else entity.polygon = viewer.entities.add({
polygon = new Cesium.PolygonGraphics({
hierarchy: new Cesium.PolygonHierarchy([point1, point2, point3])
})
});
}
The result is disturbing blinking on the map.
Updating location occurs roughly once in 40ms though seems like frequency of updates have nothing to do with it.
Anyone knows a way to make the changes smoother?
Thanks for help,
David.
You need to use Callbackproperty, it executed every frame so it won't blink and be smoother.
In typescript you can write:
let poly = viewer.entities.add({
polygon: {
hierarchy: new CallbackProperty (() => {
return new PolygonHierarchy([point1, point2,
point3]) ;
}, false)
});
We are working on a school project where the aim is to create a map in which based on the amount of zoom the layer switches from one aggregate level to a smaller aggregate level. Additionally, we have several groups of layers based on a theme for which this needs to apply. So you'd click on a theme and a new group of layers that switches based on zoom level become active and when you click another theme another group of layers become active and switch based on zoom level. This means that the themes are exclusionary, ideally you can't have more than one theme active at a time.
We tried to make this work in several ways already but without much success. Using the L.Control.Layers we were unable to group different layers together under one radio button and have them switch based on zoom since the layer control build into leaflet always splits them up into separate ones. Even using L.layerGroup to combine several layer variables or creating several layers into one variable and then adding them to the map using l.control.layer.
We also tried to use L.easyButton (https://github.com/CliffCloud/Leaflet.EasyButton). This allowed us to put the variables under one button and add a zoom based layer switching inside of it. However, the issue here is that we are unable to deactivate the functionality once activated. Which results in several of them being active at one point and overlapping each other.
If possible we would like to know if we should use a different approach or if either the leaflet control function or the use of easyButton could work and how?
This is example code for one of the buttons, which would appear several times but show a different theme:
L.easyButton( '<span class="star">★</span>', function (polygon) {
var ejerlav_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:ejerlav',
transparent: true,
styles: 'polygon',
format: 'image/png'});
var municipality_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:municipality',
transparent: true,
styles: 'polygon',
format: 'image/png'});
map.on("zoomend", function() {
if (map.getZoom() <= 10 && map.getZoom() >= 2) {
map.addLayer(municipality_polygon);
} else if (map.getZoom() > 10 || map.getZoom() < 2) {
map.removeLayer(municipality_polygon);
}
});
map.on("zoomend", function() {
if (map.getZoom() <= 11 && map.getZoom() >= 11) {
map.addLayer(ejerlav_polygon);
} else if (map.getZoom() > 11 || map.getZoom() < 11) {
map.removeLayer(ejerlav_polygon);
}
});
}).addTo(map);
If my understanding is correct, you would like to give the user the ability to switch between "themes" (some sort of group of layers that switch themselves based on the map current zoom level), possibly using Leaflet Layers Control?
And regarding the switch based on map zoom, you cannot just change the Tile Layer template URL because you use some WMS?
As for the latter functionality (switching layers within a group / theme based on map zoom), a "simple" solution would be to create your own type of layer that will listen to map "zoomend" event and change the Tile Layer WMS accordingly.
L.LayerSwitchByZoom = L.Class.extend({
initialize: function (layersArray) {
var self = this;
this._layersByZoom = layersArray;
this._maxZoom = layersArray.length - 1;
this._switchByZoomReferenced = function () {
self._switchByZoom();
};
},
onAdd: function (map) {
this._map = map;
map.on("zoomend", this._switchByZoomReferenced);
this._switchByZoom();
},
onRemove: function (map) {
map.off("zoomend", this._switchByZoomReferenced);
this._removeCurrentLayer();
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
},
_switchByZoom: function () {
var map = this._map,
z = Math.min(map.getZoom(), this._maxZoom);
this._removeCurrentLayer();
this._currentLayer = this._layersByZoom[z];
map.addLayer(this._currentLayer);
},
_removeCurrentLayer: function () {
if (this._currentLayer) {
map.removeLayer(this._currentLayer);
}
}
});
You would then instantiate that layer "theme" / group by specifying an array of layers (your Tile Layers WMS), where the array index corresponds to the zoom level at which that Tile Layer should appear.
var myLayerSwitchByZoomA = new L.LayerSwitchByZoom([
osmMapnik, // zoom 0, osmMapnik is a Tile Layer or any other layer
osmDE, // zoom 1
osmFR, // zoom 2
osmHOT // zoom 3, etc.
]);
Once this new layer type is set, you can use it in the Layers Control like any other type of Layer / Tile Layer, etc.
L.control.layers({
"OpenStreetMap": myLayerSwitchByZoomA,
"ThunderForest": myLayerSwitchByZoomB
}).addTo(map);
Demo: http://jsfiddle.net/ve2huzxw/85/
Note that you could further improve the implementation of L.LayerSwitchByZoom to avoid flickering when changing the layer after zoom end, etc.
Good Day all,
I've been working with Cesium for a bit now and I started when Primitive Collections were the thing to use. I had click and drag primitive rendering working, but now I want to upgrade Cesium and move on to entities. I moved over the code, refactored, and can click and drag to draw shapes; however, before I was able to flip the asynchronous flag and it would render as I moved the mouse. Now, I'm unable to do that. I tried setting 'allowDataSourcesToSuspendAnimation' on the viewer to false, but to no avail. Any help would be extremely appreciated.
In my naivety I forgot to add a code snippet to my question. This is in a clock tick event listener that only fires when mouse down is happening(Boolean value set to true)
var radius = Cesium.Cartesian3.distance(cartesianStartMousePosition, cartesianMousePosition);
if (radius > 0) {
if (currentEntity && currentEntity.id) {
currentEntity.position = cartesianStartMousePosition;
currentEntity.ellipse = {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: new Cesium.ColorMaterialProperty(myColor)
};
currentEntity.label = {
text: 'New Overlay',
scale: 0.35
};
overlayEntities.resumeEvents();
}
else {
currentEntity = new Cesium.Entity({
position: cartesianStartMousePosition,
ellipse: {
semiMinorAxis: radius,
semiMajorAxis: radius,
material: new Cesium.ColorMaterialProperty(myColor)
},
label: {
text: 'New Overlay',
scale: 0.35
},
isSaved: false
});
overlayEntities.add(currentEntity);
}
bDrewPrim = true;
}
It looks to me like you're doing too much work to update the entity. You only need to set the values that have changed, and you should only do that if the change was substantial enough to warrant a graphics update. Try replacing the top half of your if statement with something like this:
var lastRadius = 0;
...
if (radius > 0 && !Cesium.Math.equalsEpsilon(radius, lastRadius, Cesium.Math.EPSILON2)) {
lastRadius = radius;
if (currentEntity && currentEntity.id) {
currentEntity.ellipse.semiMinorAxis = radius;
currentEntity.ellipse.semiMajorAxis = radius;
} else {
// Same as before...
I believe the ellipsoid primitive is being built on a worker thread, so this code tries to avoid setting the new radius every tick unless a real change has been applied to it.
Also, you don't show your mouse down handler, but make sure that you're setting this flag, if you aren't already setting it:
viewer.scene.screenSpaceCameraController.enableInputs = false;
This stops the globe from spinning while you drag-select the ellipse. You can reset this to true on mouse up.
In the WinRt/WP 8.1 MapControl, how do I differentiate between when the user changed the center of the screen by swiping vs a programmatic change?
The WinRt/WP 8.1 MapControl has a CenterChanged event ( http://msdn.microsoft.com/en-us/library/windows.ui.xaml.controls.maps.mapcontrol.centerchanged.aspx ), but this does not provide any info about what caused the center change.
Is there any other way of knowing whether or not the user changed the map center?
/* To give some more context, my specific scenario is as follows:
Given an app which shows a map, I want to track the gps position of a user.
If a gps position is found, I want to put a dot on the map and center the map to that point.
If a gps position change is found, I want to center the map to that point.
If the user changes the position of the map by touch/swipe, I no longer want to center the map when the gps position changes.
I could hack this by comparing gps position and center, but he gps position latLng is a different type & precision as the Map.Center latLng. I'd prefer a simpler, less hacky solution.
*/
I solved this by setting a bool ignoreNextViewportChanges to true before calling the awaitable TrySetViewAsync and reset it to false after the async action is done.
In the event handler, I immediately break the Routine then ignoreNextViewportChanges still is true.
So in the end, it looks like:
bool ignoreNextViewportChanges;
public void HandleMapCenterChanged() {
Map.CenterChanged += (sender, args) => {
if(ignoreNextViewportChanges)
return;
//if you came here, the user has changed the location
//store this information somewhere and skip SetCenter next time
}
}
public async void SetCenter(BasicGeoposition center) {
ignoreNextViewportChanges = true;
await Map.TrySetViewAsync(new Geopoint(Center));
ignoreNextViewportChanges = false;
}
If you have the case that SetCenter might be called twice in parallel (so that the last call of SetCenter is not yet finished, but SetCenter is called again), you might need to use a counter:
int viewportChangesInProgressCounter;
public void HandleMapCenterChanged() {
Map.CenterChanged += (sender, args) => {
if(viewportChangesInProgressCounter > 0)
return;
//if you came here, the user has changed the location
//store this information somewhere and skip SetCenter next time
}
}
public async void SetCenter(BasicGeoposition center) {
viewportChangesInProgressCounter++;
await Map.TrySetViewAsync(new Geopoint(Center));
viewportChangesInProgressCounter--;
}