Mapbox handle multiple GEOJSON files with loadURL - javascript

I'm currently working on a map that's meant to load multiple layers from different sources based on a config.json file.
Each layer should also display a popup on click but for some reason I only get the popup for the last loaded layer.
I'm using the ready event on the layers to make sure all the data gets loaded and iterating through them using .eachLayer method before binding the popup but still no success and can't figure out what am I missing.
Please find below my code as well a reproduction on: plnkr.co
var myMap = function(options) {
var self = this;
this.settings = $.extend({
layersConfig: 'config.json',
layerData: 'layer',
accessToken: 'pk.eyJ1IjoibWF0dGJsaXNzIiwiYSI6ImNpb3dwczBwZjAwOTh3OWtqOWZ1aG5ob3gifQ.Ot6GdtKew9u27TROm_4A6Q'
}, options);
this.map;
this.layers;
$.ajax({
url: this.settings.layersConfig,
cache: true
}).done(function(data) {
self.init(data);
});
};
myMap.prototype = {
init: function(data) {
var self = this,
settings = this.settings;
L.mapbox.accessToken = settings.accessToken;
var map = this.map = L.mapbox.map('map', 'mapbox.streets')
.setView([54.6, -2.3], 4);
var popup = new L.Popup({
minWidth: 250
});
for (var i = 0; i < data.length; i++) {
var featureLayers = this.layers = L.mapbox.featureLayer(null, {
style: {
weight: 2,
color: data[i].color,
fillColor: data[i].color,
fillOpacity: 0.4
}
}).addTo(map);
// load layers data
featureLayers.loadURL(settings.layerData + data[i].layerId + '.json')
.on('ready', function(e) {
featureLayers.eachLayer(function(layer) {
// cache layer properties
var layerProps = layer.feature.properties;
// cache feature bounds
var bounds = layer.getBounds().toBBoxString();
// bind modal
layer.bindPopup(showPopup(layer, bounds));
});
});
}
map.on('popupopen', function() {
$('.zoom-to').on('click', function() {
var array = $(this).data('zoom').split(',');
map.fitBounds([
[array[1], array[0]],
[array[3], array[2]]
])
});
});
function showPopup(popup, bounds) {
var popupData = popup.feature.properties;
var popupLabel = popupData.NAME;
var popupStructure = '<div class="leaflet-popup-label">' + popupLabel + '</div><button class="zoom-to" data-zoom="' + bounds + '">Zoom to</button></div>'
return popupStructure;
}
}
}
var map = new myMap();

.on('ready',...)
^ Has nothing to do with an AJAX call.
You need to perform actions after the ajax call is finished, that is, inside the AJAX callback.
Here:
}).done(function(data) {
/* do stuff */
});

Found the issue.
Just replace featureLayers.eachLayer with e.target.eachLayer and the popup will show as desired.

Related

Creating popup window and displaying data

I'm moving from OpenLayers 2 to OpenLayers 6 in my project.
In the OpenLayers 2 project when I click on a feature in a
vector layer I get the description of the feature in a popup window.
Here is the code:
function createVectorLayer(layer) {
var l = new OpenLayers.Layer.Vector(
layer.Title,
{
eventListeners: {
'featureselected': function (evt) {
var f = evt.feature;
var popup = new OpenLayers.Popup.FramedCloud("popup",
//OpenLayers.LonLat.fromString(f.geometry.toShortString()),// Michael commented 25/02/2018
OpenLayers.LonLat.fromString(f.geometry.getCentroid().toShortString()),
null,
"<div style='font-size:.8em'>" + f.attributes.Description + "<br/><a href='#picturedFeatureEditor' class='ui-btn ui-mini' id='featureEditButton'>עדכון</a></div>",
null,
true
);
f.popup = popup;
map.addPopup(popup);
$("#featureEditButton").click(function () {
editableFeature = f.attributes;
editableFeatureObject = f;
initFeatureEditor();
//$.mobile.changePage("#picturedFeatureEditor");
});
},
'featureunselected': function (evt) {
var feature = evt.feature;
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}
},
}
);
return l;
}
Here is how I create a Vector layer in OpenLayers 6:
function createVectorLayer(layer) {
var source = new ol.source.Vector({
loader: dataServices.getFeatures(layer.Id,
function (response) {
if (!response) return;
var features = [];
$(response).each(function (i, j) {
let shapeObject = getShapeObject(j);
let feature = new ol.Feature({ 'geometry': shapeObject });
features.push(feature);
});
source.addFeatures(features);
},
function (jqXhr, textStatus, errorMessag) {
console.log(errorMessag);
})
});
return new ol.layer.Vector({
source: source,
style: createStyle(source)
});
}
I know that I can create a popup using Overlay and ol.interaction.Select
which is fired when the feature is clicked, but I don't know how to access the feature description when the feature is clicked to display it in the popup.
My question is how I can implement the same behaviour using OpenLayers 6 (i.e. how to implement feature popups in 6)?
You can add properties to the feature in the constructor (assuming the data is available from your dataServices):
let feature = new ol.Feature({
geometry: shapeObject,
description: ....
});
which can then be accessed using feature.get('description') or feature.getProperties().description
If you are using a Select interaction
select.on('select', function(event) {
if (event.selected.length > 0) {
var feature = event.selected[0];
var description = feature.get('description');
}
});
You can look at the ol-ext FeaturePopup.
See example: https://viglino.github.io/ol-ext/examples/popup/map.popup.feature.html
Or https://viglino.github.io/ol-ext/examples/popup/map.popup.html

How to replace old geometry with new geometry in localStorage using Google Maps API?

I have 2 data layers on my Google Map: savedLayer (used for displaying/loading saved data on the map), and drawLayer (used for drawing on the map).
I'm currently able to store a drawn polygon on the map in localStorage using the Save button, and then display it on the map from localStorage.
What I'm struggling with is updating localStorage with any changes that are made to a particular polygon afterwards. Let's say you draw 3 or 4 polygons (I assign unique IDs to them). How do I update the geometry in localStorage for the selected polygon? How do I remove it? I basically want to send new updates of that polygon to localStorage once Saved is clicked.
I know there's a setgeometry event that I can use, and I'm currently using that to detect when the a geometry in savedLayer changes, but am struggling with replacing the old geometry with the new one:
changedGeom = savedLayer.addListener('setgeometry', function(e) {
console.log('changed geometry for polygon #' + e.feature.getProperty('featureID'));
console.log(e.newGeometry);
});
JSFiddle - http://jsfiddle.net/h0f3ycf4
P.S. You can currently delete/remove polygons from map using right-click on your mouse. But once I hit Save, this change is not reflected in localStorage.
Full JS:
var map, data, drawLayer, savedLayer, changedGeom;
var currentID = 0;
var uniqueID = function() {
return ++currentID;
}
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {
lat: -33.865143,
lng: 151.209900
},
zoom: 16,
clickableIcons: false,
mapTypeId: google.maps.MapTypeId.TERRAIN,
mapTypeControl: false
});
drawLayer = new google.maps.Data({
map: map
});
savedLayer = new google.maps.Data({
map: map
});
drawLayer.setControls(['Polygon']);
drawLayer.setStyle({
editable: true,
draggable: true
});
bindDataLayerListeners(drawLayer);
savePolygons(drawLayer);
loadPolygons(savedLayer);
}
function bindDataLayerListeners(drawLayer) {
drawLayer.addListener('addfeature', function(e) {
e.feature.setProperty('featureID', uniqueID());
});
drawLayer.addListener('mouseover', function(e) {
drawLayer.revertStyle();
drawLayer.overrideStyle(e.feature, {
fillOpacity: 0.5,
editable: true,
draggable: true
});
});
}
function loadPolygons(map) {
data = JSON.parse(localStorage.getItem('geoData'));
savedLayer.forEach(function(f) {
savedLayer.remove(f);
});
savedLayer.addGeoJson(data);
savedLayer.addListener('mouseover', function(e) {
savedLayer.revertStyle();
savedLayer.overrideStyle(e.feature, {
fillOpacity: 0.5,
editable: true,
draggable: true
});
});
map.addListener('click', function() {
savedLayer.revertStyle();
});
savedLayer.addListener('rightclick', function(e) {
data = JSON.parse(localStorage.getItem('geoData'));
data.features = data.features.filter(function(feature) {
return feature.properties.featureID !== e.feature.getProperty('featureID');
});
savedLayer.remove(e.feature);
});
changedGeom = savedLayer.addListener('setgeometry', function(e) {
console.log('changed geometry for polygon #' + e.feature.getProperty('featureID'));
console.log(e.newGeometry);
});
}
function savePolygons(json) {
var btn = document.getElementById('save-edits');
btn.onclick = function() {
// if (changedGeom) {
// console.log('geometry was changed and updated');
//savedLayer.addListener('setgeometry', savePolygon);
// } else {
// if (json.features.length === 0) {
// console.log('nothing to save');
// } else {
drawLayer.toGeoJson(function(json) {
localStorage.setItem('geoData', JSON.stringify(json));
alert('Saved to localStorage');
});
// }
// }
};
}
initMap();
I didn't spend a lot of time to read your code and I didn't get the point of using two layers instead of one layer. With some console.log() statements I realized that you are always saving drawLayer to localStorage and that's why you miss saved/updated data.
I forked your jsfiddle HERE and did small modifications in savePolygons() method and seems that it is working now as expected.
In short words I make sure that I'm saving the data from drawLayer and savedLayer to localStorage so we don't miss anything.
Just for reference:
function savePolygons(json) {
var btn = document.getElementById('save-edits');
btn.onclick = function() {
var drawLayerData, savedLayerData;
drawLayer.toGeoJson(function(json) {
console.log('saving geo data drawLayer ...')
console.log(json);
drawLayerData = json;
if(typeof savedLayerData != 'undefined'){
json.features = json.features.concat(savedLayerData.features)
localStorage.setItem('geoData', JSON.stringify(json));
alert('Data was saved');
}
});
savedLayer.toGeoJson(function(json) {
console.log('saving geo data savedLayer ...')
console.log(json);
savedLayerData = json;
if(typeof drawLayerData != 'undefined'){
json.features = json.features.concat(drawLayerData.features)
localStorage.setItem('geoData', JSON.stringify(json));
alert('Data was saved');
}
});
};
}

Show the attribute inspector without saving a new feature

The requirement is to add a new feature from template picker but without applying it, can i show the attribute inspector than save the feature.
selectedTemplate = templatePicker.getSelected();
This selectedTemplate is then selected to put the points on the map than opens the attribute inspector by selecting it.
selectedTemplate.featureLayer.applyEdits([newGraphic], null, null);
Sample Code Block :
dojo.connect(drawToolbar, "onDrawEnd", function(geometry) {
//display the editable info window for newly created features
if (map.infoWindow.isShowing) {
map.infoWindow.hide();
}
drawToolbar.deactivate();
var fieldAttributes = layerFieldToAttributes(selectedTemplate.featureLayer.fields);
var newAttributes = dojo.mixin(fieldAttributes, selectedTemplate.template.prototype.attributes);
var newGraphic = new esri.Graphic(geometry, null, newAttributes);
var layerInfos = [{
'featureLayer': selectedTemplate.featureLayer,
'isEditable': true
}];
var attInspector = new esri.dijit.AttributeInspector({
layerInfos: layerInfos
}, dojo.create("div"));
selectedTemplate.featureLayer.applyEdits([newGraphic], null, null, function() {
var screenPoint = map.toScreen(getInfoWindowPositionPoint(newGraphic));
map.infoWindow.setContent(attInspector.domNode);
map.infoWindow.resize(325, 185);
map.infoWindow.show(screenPoint, map.getInfoWindowAnchor(screenPoint));
templatePicker.clearSelection();
});
dojo.connect(attInspector, "onAttributeChange", function(feature, fieldName, newFieldValue) {
feature.attributes[fieldName] = newFieldValue;
feature.getLayer().applyEdits(null, [feature], null);
});
dojo.connect(attInspector, "onDelete", function(feature) {
feature.getLayer().applyEdits(null, null, [feature]);
map.infoWindow.hide();
});
});
}
I would like my client first add the attribute to feature and (save & apply) it.
Any help would be appreciated.
Here is the sample project : https://www.dropbox.com/s/fh71g1k9nsa70nq/index-2.html.zip?dl=0
I dont think you can do that with the AttributeInspector, try creating a custom popup that will have options to save and delete/cancel, when saving fire the applyEdits, when clicking delete, remove, ect.
Content:
var content = "<input id='text1'></input> </br>" +
"<input id='text1'></input> </br>" + "<button id='submit'>Submit</button>" + "<button id='delete'>Delete</button>"
/*
var attInspector = new AttributeInspector({
layerInfos: layerInfos
}, dojo.create("div"));
*/
map.infoWindow.setTitle(selectedTemplate.featureLayer.name);
map.infoWindow.setContent(content);
map.infoWindow.resize(350, 240);
map.infoWindow.show(evt.geometry, map.getInfoWindowAnchor(evt.geometry));
Listener:
on(map.infoWindow, "show", function () {
on(dom.byId("submit"), "click", function () {
alert("I should be saving");
});
on(dom.byId("delete"), "click", function () {
alert("I should be deleting");
});
})
Check out this fiddler: https://jsfiddle.net/kreza/jpLj5y4h/2/

Google Map AddListener issue

I have written following code.. which fires Add Listener event on Place_change but i want to fire that event on page load.. which is copied from google and i have made some changes.
function _initMap(latlng){
// google map
$map = $(".lwizard-step1-map");
_latlng = latlng || new google.maps.LatLng(41.3833333,2.1833333);
if (!_STEP1_LOCATION_MAP) {
_STEP1_LOCATION_MAP = new google.maps.Map($map[0],{
center: _latlng,
zoom:11,
mapTypeId:google.maps.MapTypeId.ROADMAP,
streetViewControl:false,
scrollwheel:false
});
} else {
_STEP1_LOCATION_MAP.setCenter(_latlng);
}
if (!_STEP1_LOCATION_MARKER) {
_STEP1_LOCATION_MARKER = new google.maps.Marker({
position: _latlng,
map: _STEP1_LOCATION_MAP,
draggable: true
});
google.maps.event.addListener(_STEP1_LOCATION_MARKER, "dragend", function(event) {
var lat = event.latLng.lat();
var lng = event.latLng.lng();
$("#lwizard-step1-location-autocomplete").val('');
STEP1_PLACE.latLng = [lat,lng];
STEP1_PLACE.address = null;
$.ajax({
url: 'http://maps.googleapis.com/maps/api/geocode/json?latlng='+lat+','+lng+'&sensor=false',
success: function(response){
if ((response) && (response.results) && (response.results.length)){
$(".lwizard-step1-chosen-address").parent().show();
var $address = $(".lwizard-step1-chosen-address");
$address.text(response.results[0].formatted_address);
$("#lwizard-step1-adjust-onmap").data("location", response.results[0].geometry.location);
STEP1_PLACE.address = nc.utils.convertGoogleAddressComponents(response.results[0].address_components);
}
}
})
});
} else {
_STEP1_LOCATION_MARKER.setPosition(_latlng);
}
}
// autocomplete
$input = $("#lwizard-step1-location-autocomplete");
_STEP1_LOCATION_GA = new google.maps.places.Autocomplete($input[0],{ types: [] });
google.maps.event.addListener(_STEP1_LOCATION_GA, 'place_changed', function () {
var place = _STEP1_LOCATION_GA.getPlace();
console.log(place)
var $where = $(".lwizard-step1-chosen-address");
var found = ((place) && (place.formatted_address) && (place.geometry) && (place.id));
if (place && found) {
$("#lwizard-step1-adjust-onmap").data("location", place.geometry.location);
STEP1_PLACE.address = nc.utils.convertGoogleAddressComponents(place.address_components);
STEP1_PLACE.latLng = [place.geometry.location.lat(),place.geometry.location.lng()];
STEP1_PLACE.location = place.geometry.location;
$where.parent().show();
$where.text(place.formatted_address);
$("#lwizard-step1-right-1").show();
$("#lwizard-step1-right-2").hide();
_initMap(place.geometry.location);
} else {
$where.parent().hide();
$("#lwizard-step1-right-1").show();
$("#lwizard-step1-right-2").hide();
_initMap();
}
});
I have one textbox named lwizard-step1-location-autocomplete which shows value from session named SessionLocation on page load.
but i also want to show the map on page load of the location specified in textbox.
But the problem is the addlistener fires only on textbox change means Place_changed event.
pls. Give some suggestions or new method for it.
You can just call change event on load and than it will fired.
$('#lwizard-step1-location-autocomplete').trigger("change");
Or with js;
document.getElementById('lwizard-step1-location-autocomplete').fireEvent("onchange");

Using Declarative Bindings with API Data

I'm creating a tool which displays nearby points of interests on a map. When a marker is clicked the map should display a list of WikiPedia articles relating to that landmark.
I'm wondering if anyone can point me in the right direction as far as getting ko.observables working with my API return. I would like to avoid appending everything to the page like I am right now, I also believe it's causing some issues with my info windows not closing properly when another marker is clicked.
Some of the things I've tried was changing the $wikiData.append to an observable and then changing the contentString variable to include the data-bindings, but alas it did not work out.
The API I'm using is the WikiPedia API. Here is the code:
function mapPin(name, lat, long, text) {
this.name = ko.observable(name);
this.lat = ko.observable(lat);
this.long = ko.observable(long);
this.text = ko.observable(text);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, long),
map: mapView,
animation: google.maps.Animation.DROP
});
function toggleBounce() {
if (marker.getAnimation() != null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
}
function article(content, url) {
var self = this;
self.content = content;
self.url = url;
}
function apiData() {
var wikipediaURL = 'http://en.wikipedia.org/w/api.php?action=opensearch&search=' + name + '&format=json&callback=wikiCallback';
var wikiRequestTimeout = setTimeout(function () {
$wikiData.text ("Failed to get Wikipedia resources");
}, 5000);
$.ajax({
url: wikipediaURL,
dataType: "jsonp",
success: function (response) {
viewModel.articleList.removeAll();
var articleList = response[1];
for (var i = 0; i < articleList.length; i++) {
articleStr = articleList[i];
var url = 'http://en.wikipedia.org/wiki/' + articleStr;
viewModel.articleList.push(new article(articleStr, url));
}
clearTimeout(wikiRequestTimeout);
}
});
}
var contentString = '<!-- ko foreach: articleList --><li><a data-bind="attr: {href: url}, text: content"></a></li>';
var infowindow = new google.maps.InfoWindow({});
google.maps.event.addListener(mapView, 'click', function () {
infowindow.close();
});
google.maps.event.addListener(marker, 'click', function () {
infowindow.close();
toggleBounce();
infowindow = new google.maps.InfoWindow({
content: text + contentString
});
infowindow.open(mapView, marker);
apiData();
});
}
var mapView = new google.maps.Map(document.getElementById('map-canvas'), {
zoom: 12,
center: new google.maps.LatLng(61.196148, -149.885577),
});
var viewModel = {
articleList: ko.observableArray([]),
pins: ko.observableArray([
new mapPin("Alaska Communications", 61.196148, -149.885577, "test11"),
new mapPin("Moose's Tooth", 61.190491, -149.868937, "test2")
]),
// TODO
query: ko.observable(''),
search: function (value) {
viewModel.pins[0].removeAll();
for (var i in pins) {
if (pins[i].name.toLowerCase().indexOf(valkue.toLowerCase()) >= 0) {
this.pins.push(pins[i])
}
}
}
};
// Initiates the viewModel bindings.
ko.applyBindings(viewModel);
You can find a working version of the site here:
http://jamesiv.es/projects/map
Your articleList should be an observableArray in your view model with a binding in the view for that.
var viewModel = {
articleList: ko.observableArray([]),
pins: ko.observableArray([
new mapPin("Alaska Communications", 61.196148, -149.885577, "test11"),
new mapPin("Moose's Tooth", 61.190491, -149.868937, "test2")
]),
};
First make a little DTO:
function article(content, url) {
var self = this;
self.content = content;
self.url = url;
}
When you get the data back from our ajax call, first clear the array:
viewModel.articleList.removeAll();
Then just loop through and do something like this:
viewModel.articleList.push(new article(articleStr, url));
Do the formatting in your view with a binding like this:
<!-- ko foreach: articleList -->
<li><a data-bind="attr: {href: url}, text: content"></a></li>
<!-- /ko -->

Categories

Resources