I have an OpenLayers map that draws features in a vector layer. The features are selectable and have a popup on select. Unfortunately in a lot of cases the features overlap so it can be impossible to select some features. I think that what I need to do to fix this is change my select control so that it uses a click handler and searches the map for features at this point. What sort of function do i need to write? are there any examples of this being implemented before?
This is how the features are drawn:
var vector_Layer = new OpenLayers.Layer.Vector();
function GetFeaturesFromKMLString (strKML) {
var format = new OpenLayers.Format.KML({
'internalProjection': new OpenLayers.Projection("EPSG:900913"),
'extranalProjection': new OpenLayers.Projection("EPSG:4326")
});
return format.read(strKML);
};
vector_Layer.addFeatures(GetFeaturesFromKMLString('$newkml'));
And this is how Layers are currently selected:
var select = new OpenLayers.Control.SelectFeature(vector_Layer, {clickout: true});
vector_Layer.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect});
map.addControl(select);
select.activate();
select.handlers['feature'].stopDown = false;
select.handlers['feature'].stopUp = false;
here is the click event listener I was planning on using:
OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
defaultHandlerOptions: {
'single': true,
'double': false,
'pixelTolerance': 0,
'stopSingle': false,
'stopDouble': false
},
initialize: function(options) {
this.handlerOptions = OpenLayers.Util.extend(
{}, this.defaultHandlerOptions
);
OpenLayers.Control.prototype.initialize.apply(
this, arguments
);
this.handler = new OpenLayers.Handler.Click(
this, {
'click': this.onClick
}, this.handlerOptions
);
},
onClick: function(evt) {
//function that seachers for and selects features at this point
},
As the vector features use the OpenLayers.Geometry classes as geometry description you should have a look at Geometry, there is an intersects method which may be useful:
See: http://dev.openlayers.org/releases/OpenLayers-2.13/doc/apidocs/files/OpenLayers/Geometry/Point-js.html
You will also need to intercept the click event in order to run intersection checks.
Related
Using Leaflet, I linked to a L.easyButton a function which adds a geoJson polygon layer to a map, and that activates different functions "onEachFeature" of this layer. Among them, a function when clicking on a feature which creates a separate geoJson polygon layer with this feature and add it to a specific LayerGroup. What I would like is that after clicking on a feature, the different functions "onEachFeature" would not be activated anymore and that the user would need to push the L.easyButton again if he/she wants to do that another time. Everything works fine, except the fact to deactivate the "onEachFeature" functions. I tried to use map.removeControl(), map.removeLayer() and delete the variable directly, but none of these options work.
My code is:
L.easyButton( '<strong>Sélectionner une commune</strong>', function(){
var communes_geojson = $.getJSON("data/Adm/Adm_Communes_4326.geojson",function(communes){
var communes_new = L.geoJson(communes, {
style: stylelayer.communes,
onEachFeature: function addMyData (feature,layer){
layer.on({
click: selectcommune
});
}
});
map.addControl(communes_new);
});
info.addTo(map);
},
{position: 'topright'}).addTo(map);
function selectcommune(e) {
var layer=e.target;
var feature = e.target.feature;
var selectcommune1 = L.geoJson(feature, {
style: stylelayer.highlight
}).addTo(map);
control_map2.addOverlay({
name:feature.properties.NameFRE,
layer:selectcommune1
});
// don't work
// map.removeControl(communes_geojson);
// map.removeControl(communes_new);
// map.removeControl(layer);
// map.removeLayer(communes_geojson);
// map.removeLayer(communes_new);
// map.removeLayer(layer);
// delete communes_geojson;
// delete communes_new;
// delete layer;
Tahnk you for your help.
In selectcommune, once you do what you want (style your feature and add your overlay), you then need to go back and void out the onEachFeature function that you had added with the L.easyButton:
L.easyButton( '<strong>Sélectionner une commune</strong>', function(){
var communes_geojson = $.getJSON("data/Adm/Adm_Communes_4326.geojson",function(communes){
var communes_new = L.geoJson(communes, {
style: stylelayer.communes,
onEachFeature: function addMyData (feature,layer){
layer.on({
click: e => selectcommune(e, communes_new)
});
}
});
map.addControl(communes_new);
});
info.addTo(map);
},
function selectcommune(e, data) {
var layer=e.target;
var feature = e.target.feature;
var selectcommune1 = L.geoJson(feature, {
style: stylelayer.highlight
}).addTo(map);
control_map2.addOverlay({
name:feature.properties.NameFRE,
layer:selectcommune1
});
data.eachLayer( layer => layer.on = () => {} )
}
So you hit the easybutton, and layer.on is set for each feature to fire the selectcommune. When it fires, it removes the layer.on function (or overwrites it to do nothing), until you hit the easybutton again.
I am using Leaflet JS to draw polygons on a map.
I am able to draw the shapes, and to save the shapes to a database as GeoJSON like this:
.ts file
...
let layer = event.layer;
let geometry = layer.toGeoJSON();
this.drawLayer.addLayer(layer);
}
I call a method in the ngOnInit method to redraw the shapes:
drawPolygonShape(data) {
if (data.polygon.geometry) {
let shape = {
type: data.polygon.type,
geometry: {
type: data.polygon.geometry.type,
coordinates: data.polygon.geometry.coordinates
},
properties: {}
};
L.geoJSON(shape, {
onEachFeature: this.onEachFeature
}).addTo(this.myMap);
}
}
...
onEachFeature(feature, layer) {
layer.on('click', event => {
// layer.bindPopup('Hello World'); // Works great
let popover = this.popoverCtrl.create('MyComponent', { layer: layer });
popover.present();
});
}
I am importing MyComponent into the module file, so I know it is available. However, I am always getting the following error message:
Cannot read property 'create' of undefined
So, somewhere there is a timing or scope problem that is not correctly working with the click event.
It seems like a scope problem, as any component gives the same error. Any suggestions are greatly appreciated!
EDIT
Thank you to #ghybs I've tried adding this as a third argument to the click event, but I'm still getting the same error:
onEachFeature(feature, layer) {
layer.on('click', event => {
// layer.bindPopup('Hello World'); // Works great
let popover = this.popoverCtrl.create('MyComponent', { layer: layer });
popover.present();
}, this);
}
EDIT 2
Thank you #ghybs!
I'm curious about your second suggestion - I am setting up the map like this:
let mapOptions: any = {
position: 'topright',
draw: {
polyline: false,
...
}
}
...
let drawControl = new L.Control.Draw(mapOptions);
this.myMap.addControl(drawControl);
this.myMap.on(L.Draw.Event.CREATED, this.onCreatePolygon.bind(this));
The .bind(this) makes more sense now - onCreatePolygon is a method I am using to save the shape.
How do you suggest I delegate to the click event for each shape?
this.myMap.on(L.Draw.Event.CLICK, this.MYCLICKHANDLER.bind(this));
(it's obvious I'm not familiar working with this so I appreciate all of your time.)
In your case you have a double context issue since you also pass your onEachFeature method that will get invoked with a this context that is already different from your class instance.
You could do onEachFeature: this.onEachFeature.bind(this) together with your layer.on("click", cb, this)
You could also use event delegation by attaching the listener on your GeoJSON Layer Group instead of each individual feature, so that you get rid of one of the context:
var geoJsonData = {
"type": "Point",
"coordinates": [2.35, 48.86]
};
class Component {
constructor(mapId) {
this.myMap = L.map(mapId).setView([48.86, 2.35], 11);
L.geoJSON(geoJsonData).addTo(this.myMap).on("click", event => {
console.log(event.target); // this is the GeoJSON Layer Group.
console.log(event.layer); // this is the individual child feature.
let popover = this.popoverCtrl.create('MyComponent');
popover.present();
}, this); // Not even needing the 3rd arg since you use a fat arrow function, which does not have its own context.
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.myMap);
}
}
Component.prototype.popoverCtrl = { // Dummy popoverCtrl
create(content) {
dummyPopoverContent = content;
return {
present() {
alert(dummyPopoverContent);
},
};
},
};
var dummyPopoverContent = '';
new Component('map');
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.1/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.1/dist/leaflet-src.js" integrity="sha512-IkGU/uDhB9u9F8k+2OsA6XXoowIhOuQL1NTgNZHY1nkURnqEGlDZq3GsfmdJdKFe1k1zOc6YU2K7qY+hF9AodA==" crossorigin=""></script>
<div id="map" style="height: 180px"></div>
See also start a function when click on circle - leaflet
As for your last question, with the 1st remark above you could re-write:
this.myMap.on(L.Draw.Event.CREATED, this.onCreatePolygon.bind(this));
into:
this.myMap.on(L.Draw.Event.CREATED, this.onCreatePolygon, this);
Event delegation does not seem relevant in this case since you already attach a single listener on your map, not on individual layers.
I want leaflet to work offline, without title, to show only a grid as titles. To have all the features of Leaflet, add plugins to draw a line, pin a marker, draw a polygon, zoom in / zoom out on shapes etc.
Is there a simple way to show just a simple grid?
Here's a custom GridLayer (that's already been implemented by the Leaflet authors). All you have to do is copy L.GridLayer.DebugCoords where you would normally load a tile layer.
var map = L.map('map', {
center: [0, 0],
zoom: 0
});
L.GridLayer.DebugCoords = L.GridLayer.extend({
createTile: function (coords, done) {
var tile = document.createElement('div');
//this adds tile coordinates; you may or may not want this
tile.innerHTML = [coords.x, coords.y, coords.z].join(', ');
tile.style.outline = '1px solid red';
/* // you don't need this artificial timeout for your application
setTimeout(function () {
done(null, tile); // Syntax is 'done(error, tile)'
}, 500 + Math.random() * 1500);
*/
return tile;
}
});
L.gridLayer.debugCoords = function(opts) {
return new L.GridLayer.DebugCoords(opts);
};
map.addLayer( L.gridLayer.debugCoords() );
Stand-alone, working example: http://leafletjs.com/examples/extending/gridcoords.html
Code taken from: http://leafletjs.com/examples/extending/extending-2-layers.html
i have added the draw interaction to draw free hand polygon by default freehandCondition is SHIFT key but how can we draw if the map is opened in Mobile and Tablets.
drawOptions.type = 'Polygon';
this.draw = new ol.interaction.Draw(drawOptions);
this.draw.on('drawend', lang.hitch(this, "drawEnd"));
How can we draw? is their any other condition i can give?
There are several ways to suspend drag pan and enable free-hand drawing in OL3. Here is one way to set the freeHandCondition (where the variable shapeGeom is Point, LineString or Polygon):
function drawInteraction() {
if (shapeGeom == 'Point') {
draw = new ol.interaction.Draw({
features: drawfeature,
type: shapeGeom,
})
} else {
draw = new ol.interaction.Draw({
features: drawfeature,
type: shapeGeom,
freehandCondition: ol.events.condition.always,
condition: ol.events.condition.never,
})
}
map.addInteraction(draw);
}
When you start the draw action, suspend DragPan.
map.getInteractions().forEach(function(interaction) {
if (interaction instanceof ol.interaction.DragPan) {
interaction.setActive(false);
}
}, this);
Then, restore DragPan when the feature has been drawn.
draw.on('drawend', function(event){
map.addInteraction(new ol.interaction.DragPan)});
OL3's API docs have information for both the freeHandCondition and DragPan elements with these links.
When I break on the first line in the call back function and do this
this.active --> true
this.deactivate() --> true
this.activate() --> true
this.deactivate() --> false
HOW CAN THIS HAPPEN?
Let me explain where this occurs.
This map has two layers and each layer has an EditingToolbar associated with it.
When the user pressed polygon draw (in the EditingToolbar), its being draw on the "polygon_layer".
When the user presses line draw or point draw the layer is switched and the user now draws on the "vectors" layer.
When I switch layers, I need to activate the correct button in the EditingToolbar, example:
The user draws polygons in the "polygon_layer" and now he want to draw lines. He presses draw lines button (in the EditingToolbar associated with the polygon_layer).
I switch layers and activate draw lines button on the EditingToolbar for that layer.
After a while, the user now wants to draw polygons again, so i deactivate all buttons in this layer, switch layers and activate the draw polygon button in the
EditingToolbar for the polygon_layer. and so on.
Now when I do this enough times (3 switches) I notice that the buttons don't get deactivate anymore.
So I tried to debug and got this totally unexpected error described above ( at the very top).
Please tell me what am I doing wrong.
I've appended my code and I'll put ERROR HERE where this occurs. This code is ready to run.
You can use the HTML code below, just change the reference to the JavaScript file that I've provided ( I've provided the contents of the JavaScript file)
JS FILE:
var map;
var editing_toolbar_polygon=null;
var editing_toolbar_vector=null;
var drag_control=null;
var vectors;
var polygon_layer=null;
var epsg900913 = new OpenLayers.Projection('EPSG:900913');
var epsg4326 = new OpenLayers.Projection('EPSG:4326');
//var epsg900913 = new OpenLayers.Projection('EPSG:900913');
// var epsg4326 = new OpenLayers.Projection('EPSG:4326');
var line_control;
var polygon_control;
var renderer;
function initialize() {
line_control, renderer=OpenLayers.Util.getParameters(window.location.href).renderer;
renderer= (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
// Create the map object
map = new OpenLayers.Map('map');
//Create a Google layer
var gmap = new OpenLayers.Layer.Google(
"Google Streets", // the default
{
numZoomLevels: 20,
projection: new OpenLayers.Projection("EPSG:900913")
}
);
var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
"http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic',
projection: new OpenLayers.Projection("EPSG:4326")});
var mystyle=new OpenLayers.StyleMap({
"default": new OpenLayers.Style({
fillColor: "#66ccff",
strokeColor: "#3399ff",
graphicZIndex: 2,
strokeWidth: 5,
}),
"temporary": new OpenLayers.Style({
fillColor:"#3399ff",
strokeColor: "#3399ff",
strokeWidth:5,
pointRadius:10
})
});
polygon_layer=new OpenLayers.Layer.Vector(
"Polygon Layer",
{
//renderers:renderer,
}
);
vectors= new OpenLayers.Layer.Vector(
"Vector Layer",
{
//renderers:renderer,
}
);
editing_toolbar_polygon=new OpenLayers.Control.EditingToolbar(polygon_layer);
editing_toolbar_vector=new OpenLayers.Control.EditingToolbar(vectors);
map.addLayers([gmap,wms,vectors,polygon_layer]);
map.addControl(new OpenLayers.Control.LayerSwitcher());
//map.addControl(new OpenLayers.Control.MousePosition());
map.addControl(editing_toolbar_polygon);
map.addControl(editing_toolbar_vector);
editing_toolbar_vector.deactivate();
//for the drag control to work you need to activate it
drag_control=new OpenLayers.Control.DragFeature(vectors);
map.addControl(drag_control);
find_control(editing_toolbar_polygon.getControlsByClass(new RegExp(".*DrawFeature")),"Point").events.register("activate",null,function(e){
//ERROR HERE
this.deactivate();
var picked_button=find_same_control(editing_toolbar_vector.controls,e.object);
change_layer(polygon_layer,vectors);
change_control(editing_toolbar_polygon,editing_toolbar_vector);
picked_button.activate();
});
find_control(editing_toolbar_polygon.getControlsByClass(new RegExp(".*DrawFeature")),"Path").events.register("activate",null,function(e){
//ERROR HERE
this.deactivate();
var picked_button=find_same_control(editing_toolbar_vector.controls,e.object);
change_layer(polygon_layer,vectors);
change_control(editing_toolbar_polygon,editing_toolbar_vector);
picked_button.activate();
});
find_control(editing_toolbar_vector.getControlsByClass(new RegExp(".*DrawFeature")),"Polygon").events.register("activate",null,function(e){
//ERROR HERE
this.deactivate();
var picked_button=find_same_control(editing_toolbar_polygon.controls,e.object);
change_layer(vectors,polygon_layer);
change_control(editing_toolbar_vector,editing_toolbar_polygon);
picked_button.activate();
});
polygon_layer.events.register("beforefeatureadded",null,function(e){
polygon_layer.removeAllFeatures();
});
// line_control=new OpenLayers.Control.DrawFeature(vectors,OpenLayers.Handler.Path);
//polygon_control=new OpenLayers.Control.DrawFeature(vectors,OpenLayers.Handler.RegularPolygon);
//map.addControl(line_control);
//line_control.activate();
//map.addControl(polygon_control);
//polygon_control.activate();
// Zoom to Vancouver, BC
map.setCenter(new OpenLayers.LonLat(-123.12, 49.28).transform(epsg4326, epsg900913), 13);
}
function change_layer(current_layer,next_layer){
current_layer.setVisibility(false);
next_layer.setVisibility(true);
}
function change_control(current_control,next_control){
current_control.deactivate();
map.addControl(next_control);
}
//use this when you want to find a specific control type:
// DrawFeature cntrol has many types, Line, Polygon, Point.
// So what you do is pass an array of DrawFeature controls and a type(string), lets say "Point",
// then this function will return a DrawFeature thats specifically for drawing points
function find_control(controls,type){
var control;
for(var x in controls){
if(controls[x].displayClass.search(new RegExp(type+"$"))>0){
return controls[x];
}
}
return -1;
}
I just tried with a simple test.
When you desactivate a controller, you just remove the events of the controller.
So for OpenLayers.Control.LayerSwitcher, desactivate this controller is useless because nothing change and you can still choose a layer.
I think the best way is to remove the controller for your polygon and to add that for the lines.
When you select the "polygon_layer" :
map.addControl(editing_toolbar_polygon);
map.removeControl(editing_toolbar_vector);
When you select the "vectors" :
map.addControl(editing_toolbar_vector);
map.removeControl(editing_toolbar_polygon);