I am loading multiple points from a geojson file and want to delete duplicates which exist in the data (For some features the properties are equal despite the ID). To reach this goal I want to find out if ol.Feature objects are equal to other ol.Feature objects.
Is equality somehow defined on ol.Feature objects or do I have to define it by myself?
You should loop through every feature and get its properties. The ID will always be different, and that's why it is not possible to use the method getFeatureById (from the layer or the source) or the method getId (from a single feature).
I have created a living example which is working and removing the duplicated features when you press the button.
Notice that we are getting the properties name and tag and we convert them into a JSON variable to compare them easily, but you can select the properties that fit your needs.
var features = [];
var point1 = ol.proj.transform([-50, 4.678], 'EPSG:4326', 'EPSG:3857');
var point2 = ol.proj.transform([20, 4.678], 'EPSG:4326', 'EPSG:3857');
var feature1 = new ol.Feature({
geometry: new ol.geom.Point(point1),
name: "First",
tag: "TAG"
});
var feature2 = new ol.Feature({
geometry: new ol.geom.Point(point2),
name: "Second",
tag: "TAG"
});
features.push(feature1);
features.push(feature2);
features.push(new ol.Feature({
geometry: new ol.geom.Point(point1),
name: "First",
tag: "TAG"
}));
var vectorSource = new ol.source.Vector({
features: features
});
var vectorLayer = new ol.layer.Vector({
source: vectorSource
});
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vectorLayer
],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
document.getElementById("btn").onclick = function(){
var totalProperties = [];
vectorSource.getFeatures().forEach(function(feature){
var propertiesThis = {},
p = feature.getProperties();
for (var i in p) {
if (i === 'name' || i === 'tag') {
propertiesThis[i] = p[i];
}
}
var jsonProperties = JSON.stringify(propertiesThis);
if (totalProperties.indexOf(jsonProperties) === -1) {
totalProperties.push(jsonProperties);
} else {
vectorSource.removeFeature(feature);
console.log(propertiesThis['name'] + " feature removed")
}
});
};
<link href="https://openlayers.org/en/v3.20.1/css/ol.css" rel="stylesheet"/>
<script src="https://openlayers.org/en/v3.20.1/build/ol.js"></script>
<div id="map" class="map" tabindex="0"></div>
<button id="btn">Remove duplicates</button>
I think it depends a lot on the perspective and use case to say when features are equal which is why it is left to the user to define equality. Some might say that features are equal if they share the (exact) same geometry (1). Others might say that features need to have the same properties (2) or even both (3).
To check properties equality I would recommend to define the attributes that matter for your definition of equality. Then you can use code similar to this to check if 2 ol.Feature objects are equal:
// Define your important properties
var mySelectedProperties = ["importantProperty", "anotherImportantProperty", "propertyX"];
// Check for property equality between two ol.Feature objects
function areEqual(featureA, featureB){
var equal = true;
for(let property of mySelectedProperties){
if(featureA.get(property) != featureB.get(property)){
equal = false;
return equal ;
}
}
return equal;
}
For geometry equality you might want to check if the (x & y) coordinates are the same. Here some more considerations:
2 geometries might look the same but the coordinates are not in the same order:
E.g.: lineA: pointA-pointB and lineB: pointB-pointA
or even this: polygonA: pointA-pointB-pointC-pointA and polygonB: pointB-pointC-pointA-pointB
For some features it might make sense to say that the geometry is so close to another one that it is probably representing the same feature... (E.g. (small) measurement errors or floating point inaccuracies).
Two ol.Feature object with the exact same properties will not be equal one an other.
So yes, you need to clear the duplicates manually. You say that the ids are always unique, but the rest can sometime be the same. In that case, you could loop in your features. For each one, get a JSON string of all properties (with the exception of the id and geometry) and compare that a new collection of features.
Here's how you could do this (untested, but this could give you an idea):
var uniqueFeatures = [];
var feature;
var properties;
var json;
var jsons = [];
for (var i = 0, ii = features.length; i < ii; i++) {
feature = features[0];
// Stringify the properties of the feature
properties = feature.getProperties();
var props4json;
for (var key in properties) {
if (key !== 'id' && key !== 'geometry') {
props4json[key] = properties[key];
}
}
json = JSON.stringify(props4json);
// Check if the stringified properties exist...
// if not, we have a new unique feature.
if (jsons.indexOf(json) === -1) {
jsons.push(json);
uniqueFeatures(feature);
}
}
Related
I have quite a few Tile Layers in my map, and they are all organized into different groups (sometimes they are even nested).
I see in API there's a getLayer() method to retrieve the layer a Vector feature belongs to, and a getLayerGroup() to retrieve all groups associated with a Map.
However, I could not find anything on getting the layerGroup a layer is associated with.
Lets'say I have this situation:
var myGroup = new LayerGroup();
var myLayer = new TileLayer();
myGroup.getLayers().insertAt(0, myLayer);
Is there a way to get myGroup from myLayer?
To get the parent group of a layer would need to write your own search function, something like
function searchGroups(group, layer) {
var result;
var layers = group.getLayers().getArray();
for (var i = 0; i < layer.length; i++) {
if (layers[i] === layer) {
result = group;
} else if (layers[i] instanceof LayerGroup) {
result = searchGroup(layers[i], layer)
}
if (result) {
break;
}
}
return result;
}
then call
var myGroup = searchGroups(map.getLayerGroup(), mylayer);
The getLayers() function you linked only works for a select interaction, you cannot determine from a random feature which layer it belongs to (and it could be in more than one) without a similar search of the features in each vector layer source.
I realize this question has already been answered, but alternatively, you also have the properties attribute for your layers, so you could add an array of group names to the layer itself. For example:
let parent_group = "parent_group";
let sub_group = "sub_group";
let group = new LayerGroup({
name: parent_group,
layers: [
new LayerGroup({
name: sub_group,
layers: [
new TileLayer() {
properties: [
parent_group,
sub_group
]
}
]
})
]
})
Then it's just a matter of looking up the layer name and looking up its properties array - probably a bit more cumbersome to setup initially, but it would save having to recursively search through layerGroups.
My map contains multiple features, the ids for all these features are stored in an array: featureIds.
My application contains a button which toggles the visibililty of some of the features.
I am working on a JavaScript function reCenter() to follow this toggling. This function "zooms" out and refits the map view in accordance to the bounds of features which are now visible.
function reCenter() {
// new array for visible features
var visibleFeatures = [];
// retrieve the features which are visible and put them into the new array
for (var i = 0; i < featureIds.length; i++) {
if (map.getLayoutProperty(featureIds[i], "visibility") == "visible") {
visibleFeatures.push(map.queryRenderedFeatures(featureIds[i]));
}
}
// new array to store coordinates
coordinates = [];
// push coordinates for each visible feature to coordinates array
for (var j = 0; j < visibleFeatures.length; j++) {
coordinates.push(coord.geometry.coordinates);
}
// do fit as shown here : https://docs.mapbox.com/mapbox-gl-js/example/zoomto-linestring/
var bounds = coordinates.reduce(function (bounds, coord) {
return bounds.extend(coord);
}, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));
map.fitBounds(bounds, {
padding: 20
});
}
Despite implementing the above and following the guidance provided at https://docs.mapbox.com/mapbox-gl-js/example/zoomto-linestring/. I receive the following error: TypeError: this._sw is undefined
How can one best dyanmically retrieve all coordinates of visibile features and pass them into map.fitBounds()?
Get all your features and create a FeatureCollection and with bbox function from Turf.js get the bounds of the FeatureCollection. here is how I do that:
note: use toWgs84 if your coordinates are not wgs84.
const features = [
{
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
[your_feature_s_coordinates],
[...],
]
}
},
{another feature}
]
const FeatureCollection = {
type: "FeatureCollection",
features: features
};
map.fitBounds(bbox(toWgs84(FeatureCollection)));
Try Turf.js: js is an open-source JavaScript library used for spatial analysis.
And it provide's a method bbox (http://turfjs.org/docs/#bbox)
It take's some set of feature's and will automatically calculate bbox for you. and set that final bbox to fitbounds.
and there's a similar question asked earlier:
Mapbox GL JS getBounds()/fitBounds()
I have a model in javascript which has latitude and longitude value. I have to find a feature on the map by the ID of the element and update it's location and several other properties. My code looks like this:
function updateCoordinate(item) {
var features = source.getFeatures();
var featureToUpdate;
// find feature by custom property
for(var i=0; i< features.length; i++) {
if (features[i].get('ID') == item.ID) {
featureToUpdate = features[i];
break;
}
}
// get lon, lat from input item
var lon = item.Coordinate.Longitude;
var lat = item.Coordinate.Latitude;
// update geometry (not working)
featureToUpdate.set('Geometry', new ol.geom.Point(getPointFromLongLat(lon, lat)));
// update custom properties (working)
featureToUpdate.set('MapMarkerTitle', item.Title);
// ...
}
function getPointFromLongLat (long, lat) {
return ol.proj.transform([long, lat], 'EPSG:4326', 'EPSG:3857')
}
Am I doing something wrong? Is there a better way for this? Is there a better way to find feature by custom property?
By custom poperty I mean that the feature is getting initiated like this:
var fea = new ol.Feature({
geometry: new ol.geom.Point(getPointFromLongLat(lon, lat)),
MapMarkerTitle : 'AAA',
// ...
})
source.addFeatures([fea]);
The custom properties are getting updated but the coordinate doesn't seem to update. Will the feature be redrawn after updating position? The label is however redrawn so I think yes.
UPDATE
After some debugging I found out that, I mispelled the 'geometry' property with uppercase.
Actually:
featureToUpdate.set('geometry', new ol.geom.Point(getPointFromLongLat(lon, lat)));
does set the new position and update the location right away. I would still like to know if what I am doing is the good way or there is better. Thanks!
You can simplify it to:
function updateCoordinate(item) {
var featureToUpdate = source.getFeatureById(item.ID);
var coord = getPointFromLongLat(item.Coordinate.Longitude, item.Coordinate.Latitude);
featureToUpdate.getGeometry().setCoordinates(coord);
featureToUpdate.set('MapMarkerTitle', item.Title);
}
I'm trying to get the Paths of a polygon, and then set them to another polygon like that.
newpoly = new google.maps.Polygon({
paths:poly.getPaths()
});
Isn't this suppose to work ? It gives me this error in the console.
Invalid value for constructor parameter 0: [object Object]
Try adding the following before you instantiate the polygon object
var triangleCoords = [
new google.maps.LatLng(25.774252, -80.190262),
new google.maps.LatLng(18.466465, -66.118292),
new google.maps.LatLng(32.321384, -64.75737),
new google.maps.LatLng(25.774252, -80.190262)
];
Now use you code and replace the poly.getPaths() - Assuming the rest of your code works.
newpoly = new google.maps.Polygon({
paths:triangleCoords //there are probably more method to add here
});
If it works then you know there is something wrong with poly.getPaths(). Use this as reference https://developers.google.com/maps/documentation/javascript/overlays#PolygonOptions.
Remember that we can only use the code that was provide to formulate an solution.
It would help if you can show the code for poly object and if poly.getPaths() return anything. All I can recomand si to debug it in detail like this:
Do you hace any error if you comment paths:poly:Paths();
Console.log(poly); return a google map polygon?
Console.log(poly.getPaths()) return an array of paths?
If yes, you can try to create an array from poly.getPaths then pass it to newpoly.
Get the coords of first polygon this way (assuming that the two polygons were already created):
//store polygon path
var vertices = firstPolygon.getPath();
// Iterate over the vertices.
pathOfFirstPolygon = [];
for (var i =0; i < vertices.getLength(); i++) {
var xy = vertices.getAt(i);
item = {};
item["lat"] = xy.lat();
item["lng"] = xy.lng();
pathOfFirstPolygon.push(item);
};
//Set path of the second polygon
secondPolygon.setPath(pathOfFirstPolygon);
I have a connection to a database(db). I am getting the lon, lat and name from the db and stroing them:
while ($row_ChartRS = mysql_fetch_array($sql1))
{
$latitude=$row_ChartRS['latitude'];
$longitude=$row_ChartRS['longitude'];
$bus_name =$row_ChartRS['short_name'];
//echo $latitude.'--'.$longitude.'<br>';
echo $bus_name;
I then create a map to display the data. The markers are working fine for all lat, lon locations. Code:
function init()
{
projLonLat = new OpenLayers.Projection("EPSG:4326"); // WGS 1984
projMercator = new OpenLayers.Projection("EPSG:900913"); // Spherical Mercator
overviewMap = new OpenLayers.Control.OverviewMap();
//adding scale ruler
scale = new OpenLayers.Control.ScaleLine();
scale.geodesic = true; // get the scale projection right, at least on small
map = new OpenLayers.Map('demoMap',
{ controls: [ new OpenLayers.Control.Navigation(), // direct panning via mouse drag
new OpenLayers.Control.Attribution(), // attribution text
new OpenLayers.Control.MousePosition(), // where am i?
new OpenLayers.Control.LayerSwitcher(), // switch between layers
new OpenLayers.Control.PanZoomBar(), // larger navigation control
scale,
overviewMap // overview map
]
}
);
map.addLayer(new OpenLayers.Layer.OSM.Mapnik("Mapnik"));
map.addLayer(new OpenLayers.Layer.OSM.Osmarender("Osmarender"));
//Create an explicit OverviewMap object and maximize its size after adding it to the map so that it shows
//as activated by default.
overviewMap.maximizeControl();
//Adding a marker
markers = new OpenLayers.Layer.Markers("Vehicles");
map.addLayer(markers);
vectorLayer = new OpenLayers.Layer.Vector('Routes');
map.addLayer(vectorLayer);
for (k in Locations)
{
//adding a popup for the marker
var feature = new OpenLayers.Feature(markers, new OpenLayers.LonLat(Locations[k].lon, Locations[k].lat).transform(projLonLat,projMercator));
//true to close the box
feature.closeBox = true;
feature.popupClass = new OpenLayers.Class(OpenLayers.Popup.AnchoredBubble,
{
//create the size of the box
'autoSize': true,
'maxSize': new OpenLayers.Size(100,100)
});
//add info into box
for (z in names)
{
feature.data.popup = new OpenLayers.Feature(new OpenLayers.LonLat(names[z]).transform(projLonLat,projMercator));
}
//puts a scroll button on box to scroll down to txt
//feature.data.overflow = "auto";
marker = feature.createMarker();
marker.display(true);
markerClick = function (evt) {
if (this.popup == null) {
this.popup = this.createPopup(this.closeBox);
map.addPopup(this.popup);
this.popup.show();
} else {
this.popup.toggle();
}
currentPopup = this.popup;
OpenLayers.Event.stop(evt);
};
marker.events.register("mousedown", feature, markerClick);
markers.addMarker(marker);
map.setCenter(new OpenLayers.LonLat(Locations[k].lon, Locations[k].lat).transform(projLonLat,projMercator), zoom);
var lonLat1 = new OpenLayers.LonLat(Locations[k].lon,Locations[k].lat).transform(new OpenLayers.Projection('EPSG:4326'), map.getProjectionObject());
var pos2=new OpenLayers.Geometry.Point(lonLat1.lon,lonLat1.lat);
points1.push(pos2);
//Uncomment to put boxes in when map opens
//feature.popup = feature.createPopup(feature.closeBox);
//map.addPopup(feature.popup);
//feature.popup.show()
}
var lineString = new OpenLayers.Geometry.LineString(points1);
var lineFeature = new OpenLayers.Feature.Vector(lineString,'',style_green);
vectorLayer.addFeatures([lineFeature]);
map.setCenter(lonLat1,zoom);
} //function
However the name in the popup marker is the same for all markers. i.e. the last name pulled from the db. Can anyone please help with this - I have spent 3 full days trying to fix it!
Thanks in advance!
A few comments:
The PHP code you’ve posted is completely irrelevant, since it’s not seen to be used anywhere.
The objects names and Locations aren’t declared anywhere in the code you posted. What do they contain?
In the code quoted below, you’re creating multiple new Feature objects, but you assign them all to the same property (thereby overwriting that property each time). Is that intentional?
//add info into box
for (z in names) {
feature.data.popup = new OpenLayers.Feature(new OpenLayers.LonLat(names[z]).transform(projLonLat,projMercator));
}
Edit:
This does appear to be where it’s going wrong. You should remove the for...z loop, and replace it with the following code:
//add info into box
feature.data.popup = new OpenLayers.Feature(new OpenLayers.LonLat(names[k]).transform(projLonLat,projMercator));
Since in PHP, you’re using the same index ($v) to fill both arrays, it makes sense to use the same index to read them in javascript...
Aside from that, using the for...in loop on Javascript arrays is not considered good practice, for a number of reasons. It’s better to use the following:
for (k = 0; k < Locations.length; k += 1) {
// your code
}
i had the same problem , and i solve it ...
the problem is overwrite
you don't have to loop inside your function , do the loop for function for example:
function init(z)
{
feature.data.popup = new OpenLayers.Feature(new OpenLayers.LonLat(names[z]).transform(projLonLat,projMercator));
}
for (z in names)
{
init(z)
}