Trouble binding events/popups to existing leaflet geojson layer - json

I am trying to add local json data to a GeoJson layer in Leaflet, and then (for now) bind a popup to each feature in the json. The trouble is that I am unable to first create a geojson layer, and then later bind popups. Is there any way to do this? I am only able to create the layer and add the popups at the same time. What I have so far:
Create the map.
map = new L.Map('map');
Grab the local json file:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"name": "Denver",
"amenity": "Baseball Stadium",
"popupContent": "This is where the Rockies play!"
},
"geometry": {
"type": "Point",
"coordinates": [-102.99404, 37.75621]
}
},
{
"type": "Feature",
"properties": {
"name": "Baltimore",
"amenity": "Baseball Stadium",
"popupContent": "This is where the Orioles play!"
},
"geometry": {
"type": "Point",
"coordinates": [-76.6167, 39.2833]
}
}
]
}
and send json through to plotData():
function plotData( data )
{
var pointLayer = L.geoJson().addTo(map);
// 1. works
L.geoJson(data, {
onEachFeature: onEachFeature
}).addTo(map);
// 2. does not bind popups
pointLayer.addData( data, {
onEachFeature: onEachFeature
}
);
// 3. Error - invalid GeoJson Object
pointLayer.addData( L.geoJson(data, {
onEachFeature: onEachFeature
})
);
}
function onEachFeature( feature, layer )
{
layer.bindPopup( feature.properties.name );
}
The markers display just fine on the map for scenario 1 and 2 (with 1 also displaying popups). Now, is there any reason why I should not be trying to first create the layer and then bind actions to the features? Is it better practice to just do what I have stated in 1?

The third option won't work, because you're feeding L.Layer object where a GeoJSON object should go. L.GeoJSON.addData() function does not have onEachFeature parameter. Basically, when you have processed a GeoJSON, its feature properties are gone.
There are two ways to proceed.
// create empty GeoJSON layer
var pointLayer = L.geoJson(null, { onEachFeature: storeName }).addTo(map);
// add data to it later, invoking storeName() function
pointLayer.addData(data);
// which stores names
function storeName(f, l) { l._gname = f.properties.name; }
// and when you're ready...
pointLayer.eachLayer(addPopupFromGName);
// add popups with stored name
function addPopupFromGName(l) { l.bindPopup(l._gname); }
Or just add onEachFeature function to L.GeoJSON layer options:
var pointLayer = L.geoJson(null, { onEachFeature: onEachFeature }).addTo(map);

Related

Convert JSON to GeoJSON frontend

I have json from an open data API that I need to convert into geojson so that it can be displayed as layer on my Mapbox map. I am using Mapbox GL JS library: https://docs.mapbox.com/mapbox-gl-js/api/. Here's the link to the open data json api: https://data.cityofnewyork.us/resource/64uk-42ks.json.
I can successfully fetch the json api and print it to console, but now I need to convert it to a geojson. I know that I can do this entirely frontend because it is open data.
Here's my code:
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/niki12step/ck8q9fgpx00d91ipipual7mrl', // replace this with your style URL
center: [-73.961581,40.683868],
zoom: 9.5
})
var pluto_url = 'https://data.cityofnewyork.us/resource/64uk-42ks.json'
getData();
async function getData () {
await fetch(pluto_url)
.then(response => response.json())
.then(data => console.log(data))
}
A GeoJSON file typically looks like this:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [75, 25]
},
"properties": {
"name": "earth"
}
}
]
}
"features" is the list of all your features. Each feature is a object with the keys "type", "geometry" and "properties" and eventually "id".
That means you have to loop through all data points in your JSON file and convert it to this format. This could look like this:
const pluto_url = 'https://data.cityofnewyork.us/resource/64uk-42ks.json';
getData();
async function getData () {
let mygeojson = {"type": "FeatureCollection", "features": []}
await fetch(pluto_url)
.then(response => response.json())
.then(data => {
for(let point of data){
let coordinate = [parseFloat(point.longitude), parseFloat(point.latitude)];
let properties = point;
delete properties.longitude;
delete properties.latitude;
let feature = {"type": "Feature", "geometry": {"type": "Point", "coordinates": coordinate}, "properties": properties}
mygeojson.features.push(feature);
}
});
console.log(mygeojson);
}
I assumed that you only have Point geometries. I used parsedFloat() because coordinates are stored as String in your file but the GeoJSON format required float values. With delete properties.longitude and delete properties.latitude I achieve that the coordinates are not needlessly part of the properties field.

Google Embed API: GEO chart - show specific country

I'm using Google Embed API to show data from google analytics visually.
I was trying to display only a specific country to show users from each of its regions.
I create a "DataChart" which has a "query" and "chart" object. In the chart object, you specify a type of chart, and some extra options.
If I choose "GEO", then it will use the "Geocoding" api, as I've understood it.
I am not able to show the country (Sweden) with its regions however, I don't know what to specify in the chart "options" object.
var location = new gapi.analytics.googleCharts.DataChart({
query: {
'ids': viewId,
'start-date': '90daysAgo',
'end-date': 'today',
'metrics': 'ga:users',
'sort': '-ga:users',
'dimensions': 'ga:region',
'max-results': 10
},
chart: {
'container': 'location',
'type': 'GEO',
'options': {
region: 150, // <-- Europe
country: 'SE', // <-- just guessing
}
}
});
This shows the whole world. If I remove "country", it shows Europe, with the top part cropped away. So I haven't specified "country" in the correct way (I am only guessing since there is no info).
The only info I can find on the GEO chart is here Visualization: GeoChart, but it's not specific for the Embed API.
So does anyone have a solution for this case, and is there info on different properties for the chart object? ( For the query object, there is Dimensions & Metrics Explorer )
Update:
The main question was solved with a below answer:
'options': {
region: 'SE',
resolution: 'provinces'
}
, but the data is not displayed in the regions, so if you have any clues around that, you could perhaps mention it as a comment.
Here is part of the data response from the query (with regions):
"dataTable": {
"cols": [
{
"id": "ga:region",
"label": "ga:region",
"type": "string"
},
{
"id": "ga:users",
"label": "ga:users",
"type": "number"
}
],
"rows": [
{
"c": [
{
"v": "Stockholm County"
},
{
"v": "15"
}
]
},
{
"c": [
{
"v": "Vastra Gotaland County"
},
{
"v": "6"
}
]
},
here are the only configuration options for the GeoChart that I'm aware of...
to display only sweden...
var options = {
region: 'SE'
};
(remove the country option)
see following working snippet...
google.charts.load('current', {
'packages':['geochart'],
'mapsApiKey': 'AIzaSyD-9tSrke72PouQMnMX-a7eZSW0jkFMBWY'
});
google.charts.setOnLoadCallback(drawRegionsMap);
function drawRegionsMap() {
var data = google.visualization.arrayToDataTable([
['Country', 'Popularity'],
]);
var options = {
region: 'SE',
resolution: 'provinces'
};
var chart = new google.visualization.GeoChart(document.getElementById('chart'));
chart.draw(data, options);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart"></div>

Leaflet. How I can get JSON marker property by Latitude and Longitude?

I have my custom 'latitude' and 'longitude' variable.
var custom_loc = [50.34434, 63.23442]
And also I have JSON data with points in GeoJSON format.
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [50.34434, 63.23442]
},
"properties": {
"id" : "1",
"name": "Good place"
}
}
How I can find JSON marker by "custom_loc" and get JSON property (for e.x. "ID")?
I use leaflet.js in my project.
You can use a markers getLatLng() method to access its latlng and then match it with your custom location. To bind the id to the layer your best bet is to add the geoJSON feature to a L.GeoJSON layer and access the id via layer.feature.properties.id or bind the id to the layer via onEachFeature method passed into L.GeoJSON options.
Then whenever you want to find the layer with the matching latlng just loop through your geojson layer using the eachlayer method e.g.:
var custom_loc = [50.34434, 63.23442];
var geoJSON = L.geoJson(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [50.34434, 63.23442]
},
"properties": {
"id" : "1",
"name": "Good place"
}
}
]
},
{
onEachFeature: function(feature, layer) {
layer.options.id = feature.properties.id;
}
}
);
map.addLayer(geoJSON);
geoJSON.eachLayer(l => {
var coords = l.feature.geometry.coordinates;
var latlng = l.getLatLng();
if (latlng.lat === custom_loc[1] && latlng.lng === custom_loc[0]) {
console.log(`Latlng match: ${l.options.id}`)
}
if (coords[0] === custom_loc[0] && coords[1] === custom_loc[1]) {
console.log(`Latlng match: ${l.options.id}`);
}
});

How do I customize knockout mapping creation in nested model?

Completely new to Knockout and I am trying to map a JSON response from the server to specific models using the knockout mapping plugin. The models are nested and I'm trying to override object construction using the create callback even in the nested models. However, it doesn't appear that my mapping options are being read properly. Example JSON:
{
"EmployeeFeedbackRequestSubmissions": [
{
"EmployeeFeedbackRequestSubmissionId": 0,
"Employee": "John Smith0",
"EmployeesWorkedWith": [
{
"EmployeeName": "Joe Smith",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Michael Jones",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Jason Smith",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
},
{
"EmployeeName": "Robert Will",
"ProjectsWorked": [
{
"ProjectName": "Document Management Console"
},
{
"ProjectName": "Performance Eval Automation"
},
{
"ProjectName": "Business Tax Extensions"
}
]
}
]
}
// more EmployeeFeedbackRequestSubmissions
]
}
Mapping options:
var mappingOptions = {
// overriding default creation/initialization code
'EmployeeFeedbackRequestSubmissions': {
create: function (options) {
return (new(function () {
this.EmployeeHeading = ko.computed(function () {
return "Performance Evaluation Employee: " + this.Employee();
}, this);
ko.mapping.fromJS(options.data, {}, this);
})());
},
'EmployeesWorkedWith': {
create: function (options) {
return new instance.EmployeesWorkedWithModel(options.data);
}
}
}
};
Sample fiddle with full example: http://jsfiddle.net/jeades/9ejJq/2/
The result should be the ability to use the computed nameUpper from the EmployeesWorkedWithModel. I'm also open to suggestions about a better way to do this as this may not be the best way to handle this.
You were almost there. Straight to it working: http://jsfiddle.net/jiggle/na93A/
The mappings options object doesn't need to be nested, the mapping plug in will look up the mapping from the name, when you pass them to ko.mapping.fromJSON
So your mapping options object should be single level:
var self = this;
self.mappingOptions = {
// overriding default creation/initialization code
'EmployeeFeedbackRequestSubmissions': {
create: function (options) {
return (new(function () {
this.EmployeeHeading = ko.computed(function () {
return "Performance Evaluation Employee: " + this.Employee();
}, this);
ko.mapping.fromJS(options.data, self.mappingOptions, this);
})());
}
},
'EmployeesWorkedWith': {
create: function (options) {
// return new instance.EmployeesWorkedWithModel(options);
return (new(function(){
ko.mapping.fromJS(options.data, {}, this);
this.nameUpper = ko.computed(function () {
return this.EmployeeName().toUpperCase();
}, this);
})());
}
}
};
Notice I have used "self" as your local reference to 'this' instead of 'instance', just to make the code easier to read (as you used 'instance' in the main viewmodel).
I have also made the mappingOptions object part of the FeedbackViewModel, as we need to pass this into the mapping.fromJS call so when it sees the 'EmployeesWorkedWith' level in the data it will have the mappingOptions for it.
From:
ko.mapping.fromJS(options.data, {}, this);
To:
ko.mapping.fromJS(options.data, self.mappingOptions, this);
You can then move your creation code for 'EmployeesWorkedWith' level into the create (you could call a function, but I've kept it together in the mappingOptions as shown above, like the way you were creating the 'EmployeeFeedbackRequestSubmissions' level.
You can then get rid of the instance.EmployeesWorkedWithModel function altogether.
A working fiddle can be found here:
http://jsfiddle.net/jiggle/na93A/
Alternatively, you could create separate mappingOptions object when you are in the create for 'EmployeeFeedbackRequestSubmissions' and not have the mappings for all levels in one object, which can be seen in this fiddle http://jsfiddle.net/jiggle/Avam7/
Depends on your coding style which way you prefer, and would be important to separate them out if you had different mapping needs for different levels and they had the same collection name.
eg.
Employees
Employee
Employees (you might need different computeds, etc. at this level)
If so, you would use the second option (separate the mappingOptions and pass to the level that will use it)
I've added some console.log statements to the fiddles so you can see values as the code runs in the console, which will help to understand how it's working.
Hope it helps.
Nice thing with ko.mapping is how automated the process can be.
Check out the results in http://jsfiddle.net/9ejJq/26/
You'll note how we only use one declared mapping to kick things off.
feedbackMappingOptions = {
create: function (options) {
return new FeedbackViewModel(options.data);
}
};
From there on, each view model triggers a mapping for their child objects. You could go as far as creating a mapping option for each or, as you see for the final ProjectsWorked object under the EmployeesWorkedWith, we just throw the data right at a mapping and ko.mapping does the rest. Hope this helped.

How to populate model data from json file

I currently have to code below and a static json file. However how can I set my model defaults to the data in the json file? My JSON file has a few pages - I want to be able to get defaults and set defaults.
var PageModel = Backbone.Model.extend({
initialize: function () {
console.log('initiliazed model');
},
url: "data/data.json",
defaults: function() {
return PageView.defaultsFromJSON;
}
});
var PageView = Backbone.View.extend ({
initialize: function () {
console.log('initiliazed view')
_.bindAll(this);
this.model.fetch();
this.render();
this.model.on('change',this.render);
},
el : '#ev-wrapper',
render: function () {
$('#ev-wrapper').append(Handlebars.compile($('#ev-template').html())(this.model.toJSON()));
$('.ev-asset-loader').fadeOut('slow', function (event) {
this.remove();
});
}
});
pageModel = new PageView({model: new PageModel()});
json file -
{
"page":[{
"id":"p05",
"title":"ptitle1",
"text":"pinitialtext"
},
{
"id":"p10",
"title":"ptitle2",
"text":"pinitialtext"
}]
}
Are you rendering the page with a server-side language ? If yes, inject a JSON string into the view containing your defaults, and fill your model with it.
var data = <?php echo $json ?>,
model = new PageModel(data),
view = new PageView({model: model, el : $('#ev-wrapper')[0]});
If you are not using a server-side language, I think you could issue an AJAX request with JQuery to load your JSON data, but this would be the same as calling fetch.
I can't see a way to "include" the JSON file another way.
I am trying to solve a similar problem (Populating Backbone models from a static JSON file for a demo).
I came across an example on the Backbone.Leaflet library:
https://github.com/LuizArmesto/backbone.leaflet/blob/master/examples/map.html
// This isn't the backbone way, but we want to keep this example
// as simple as possible.
$( '#render' ).click( function () {
geoCollection.reset( JSON.parse( $( '#geoJSON' ).val() ) );
});
In this example, the ID in question (#geoJSON) is a text area that houses the JSON the author (LuizArmesto) is trying to load into the model.
<textarea id="geoJSON">
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[-46.6155, -23.5023], [-46.6193, -23.5030], [-46.6247, -23.5073], [-46.6252, -23.5117], [-46.6218, -23.5115], [-46.6154, -23.5080], [-46.6150, -23.5037], [-46.6155, -23.5023]]]
},
"properties": {}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [[-46.6318, -23.4900], [-46.6256, -23.4916], [-46.6200, -23.4900], [-46.6100, -23.4900]]
},
"properties": {}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-46.6368, -23.5100]
},
"properties": {}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-46.6156, -23.5016]
},
"properties": {}
}
]
}
</textarea>
As stated in his comments, this isn't idiomatic of backbone (or "the backbone way"), but it works great for little side projects that don't need a server.