Adding graphic to MapView after async operation - javascript

I'm trying to pull some json data from Geoserver utilizing ESRI's Arcgis Javascript 4.4 API. All of the components log to the console, but the view.graphics.add() throws this error:
Uncaught TypeError: b.spatialReference.equals is not a function
at e._projectGeometry (MapView.js:505)
at e.doRender (MapView.js:503)
at e.b.processRender (MapView.js:293)
at b.renderChild (MapView.js:500)
at b.e.renderChildren (MapView.js:290)
at b.e.doRender (MapView.js:286)
at b.processRender (MapView.js:293)
at n.renderChild (MapView.js:427)
at n.e.renderChildren (MapView.js:290)
at n.e.doRender (MapView.js:286)
I'm trying to understand why what I am attempting is not working as expected, and any help would be appreciated. Code is below:
function fetchJson(url) {
return xhr.get({
url: url,
handleAs:"json",
})
}
var map = new Map({
basemap: "dark-gray"
});
var view = new MapView({
container: "view",
map: map,
zoom: 11,
center: [-84.34, 33.93],
padding: {
left: 320
}
});
view.then(function () {
fetchJson(samplejsonlink)
.then(function (data) {
console.log(data.features[0].geometry.coordinates)
var polyline = {
type: "polyline",
paths: data.features[0].geometry.coordinates,
spatialReference: {wkid : 4326}
}
console.log(polyline);
var polylineSymbol = {
type: "simple-line",
color: [226, 119, 40],
width: 4
};
console.log(polylineSymbol);
var polylineAtt = {
Name: "ExampleName",
};
console.log(polylineAtt)
var polylineGraphic = new Graphic({
geometry: polyline,
symbol: polylineSymbol,
attributes: polylineAtt
})
console.log(polylineGraphic);
view.graphics.add(polylineGraphic);
})
})

Your codes are working when I use this coordinates as paths. Maybe your coordinates from data.features[0].geometry.coordinates is not match the Polyline coordinates format.
view.then(function () {
var polyline = {
type: "polyline",
paths: [
[-111.30, 52.68],
[-98, 49.5],
[-93.94, 29.89]
],
spatialReference: { wkid: 4326 }
}
console.log(polyline);
var polylineSymbol = {
type: "simple-line",
color: [226, 119, 40],
width: 4
};
console.log(polylineSymbol);
var polylineAtt = {
Name: "ExampleName",
};
console.log(polylineAtt)
var polylineGraphic = new Graphic({
geometry: polyline,
symbol: polylineSymbol,
attributes: polylineAtt
})
console.log(polylineGraphic);
view.graphics.add(polylineGraphic);
});

Related

Mapbox problem with creating PopUp for each cluster/marker with dynamic data

I have a problem where I am fetching my data from my API using WordPress. I have multiple clusters/markers on my map. But I am not able to create for each location a PopUp marker with the data coming from the API. I am facing some scoping issue where I want to have one popup dynamically show the data based ont he clicked marker if that makes sense.
Is there a trick on how to solve this?
import mapboxgl from 'mapbox-gl';
import MapboxLanguage from '#mapbox/mapbox-gl-language';
import apiFetch from '#wordpress/api-fetch';
(() => {
const mapContainer = document.querySelector('[data-gewoon-wonen]');
if (!mapContainer) {
return;
}
mapboxgl.accessToken = process.env.MAP_TOKEN_KEY;
const center = [4.387733, 51.862419];
const locations = {
type: 'FeatureCollection',
features: []
};
apiFetch({ path: '/wp-json/wp/v2/map?_embed' }).then((maps) => {
maps.forEach((item) => {
const {
id,
title: { rendered: title },
_embedded,
acf
} = item;
const image =
_embedded && _embedded['wp:featuredmedia'][0]?.source_url;
const {
map_location_subtitle: subtitle,
map_location_delivery: delivery,
map_location_project: project,
map_location_content: description,
map_location_coordinates_lat: lat,
map_location_coordinates_lng: lng,
map_location_status: status,
map_location_website: website
} = acf;
const getStatus = (currentStatus) => {
let statusObj = {
bouwfase: 'marker-gray',
planfase: 'marker-bright-pink',
opgeleverd: 'marker-bright-blue',
default: ''
};
let icon = statusObj[currentStatus] || statusObj['default'];
return icon;
};
const object = {
type: 'Feature',
properties: {
id,
status,
image,
icon: getStatus(status),
title,
subtitle,
project,
website,
delivery,
description
},
geometry: {
type: 'Point',
coordinates: [lng, lat]
}
};
locations.features.push(object);
});
});
const map = new mapboxgl.Map({
container: mapContainer,
style: 'mapbox://styles/theme/clcz9eocm000p14o3vh42tqfj',
center,
zoom: 10,
minZoom: 10,
maxZoom: 18,
attributionControl: false,
cooperativeGestures: true
});
map.addControl(
new MapboxLanguage({
defaultLanguage: 'mul'
})
);
map.on('load', () => {
// Add a new source from our GeoJSON data and
// set the 'cluster' option to true. GL-JS will
// add the point_count property to your source data.
map.addSource('locations', {
type: 'geojson',
// Point to GeoJSON data. This example visualizes all M1.0+ locations
// from 12/22/15 to 1/21/16 as logged by USGS' Earthquake hazards program.
data: locations,
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'locations',
filter: ['has', 'point_count'],
paint: {
// Use step expressions (https://docs.mapbox.com/mapbox-gl-js/style-spec/#expressions-step)
// with three steps to implement three types of circles:
// * Blue, 20px circles when point count is less than 100
// * Yellow, 30px circles when point count is between 100 and 750
// * Pink, 40px circles when point count is greater than or equal to 750
'circle-color': [
'step',
['get', 'point_count'],
'#51bbd6',
100,
'#f1f075',
750,
'#f28cb1'
],
'circle-radius': [
'step',
['get', 'point_count'],
20,
100,
30,
750,
40
]
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'locations',
filter: ['has', 'point_count'],
layout: {
'text-field': ['get', 'point_count_abbreviated'],
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12
}
});
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'locations',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 4,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff'
}
});
// inspect a cluster on click
map.on('click', 'clusters', (e) => {
const features = map.queryRenderedFeatures(e.point, {
layers: ['clusters']
});
const clusterId = features[0].properties.cluster_id;
map.getSource('locations').getClusterExpansionZoom(
clusterId,
(err, zoom) => {
if (err) return;
map.easeTo({
center: features[0].geometry.coordinates,
zoom: zoom
});
}
);
});
// When a click event occurs on a feature in
// the unclustered-point layer, open a popup at
// the location of the feature, with
// description HTML from its properties.
map.on('click', 'unclustered-point', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const mag = e.features[0].properties.mag;
const tsunami =
e.features[0].properties.tsunami === 1 ? 'yes' : 'no';
// Ensure that if the map is zoomed out such that
// multiple copies of the feature are visible, the
// popup appears over the copy being pointed to.
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(
`magnitude: ${mag}<br>Was there a tsunami?: ${tsunami}`
)
.addTo(map);
});
map.on('mouseenter', 'clusters', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'clusters', () => {
map.getCanvas().style.cursor = '';
});
});
})();

mapbox geojson description for popup showing 'undefined'

I have the following code but when I click on a point to open a popup it returns 'undefined' and I cannot seem to work out why.
I'm pulling the description field from the geoJSON external source which I have control over but for some reason it just does not want to populate the description HTML in my array. I took the example for the popup from the mapbox website so I know it works there. I have checked, rechecked and triple checked but I think I cannot see the tree for the forest lol.
Could someone maybe help me please? thanks.
<script>
// ajax loading gif
$(document).ready(function () {
setTimeout(function () {
$("#ajax-loader").removeClass("is-active");
}, 6000);
});
// vars
var initialOpacity = 0.2;
var opacity = initialOpacity;
// mapbox api
mapboxgl.accessToken = "hidden_api_key";
var map = new mapboxgl.Map({
container: "map", // container ID
style: "mapbox://styles/mapbox/dark-v10",
center: [-2.582861, 53.5154517],
zoom: 5,
maxZoom: 16,
minZoom: 0,
});
map.on("load", function () {
// get hotspot locations
map.addSource("hotspot_locations", {
type: "geojson",
data: "https://netmaker.io/dashboard/public_actions.php?a=ajax_helium_miners_location",
cluster: false,
clusterMaxZoom: 10, // max zoom to cluster points on
clusterRadius: 50, // radius of each cluster when clustering points (defaults to 50)
});
// add 300m circle around each hotspot
map.addLayer({
id: "circle500",
type: "circle",
source: "hotspot_locations",
paint: {
"circle-radius": {
stops: [
[0, 1],
[16, 600],
],
base: 2,
},
"circle-color": "green",
"circle-opacity": 0.1,
"circle-stroke-width": 0,
"circle-stroke-color": "white",
},
});
// add hotspot location
map.addLayer({
id: "hotspots-layer",
type: "circle",
source: "hotspot_locations",
paint: {
"circle-radius": 2,
"circle-stroke-width": 2,
// "circle-color": "#36d293",
"circle-color": "white",
"circle-stroke-color": [
"match",
["get", "status"],
"online",
"#36d293",
"offline",
"#d23636",
"orange", // other
],
// "circle-stroke-color": '#36d293',
},
});
});
// ajax call hotspots location by name
var customData;
$.ajax({
async: false,
type: "GET",
global: false,
dataType: "json",
url: "https://netmaker.io/dashboard/public_actions.php?a=ajax_helium_miners_location_customdata",
success: function (data) {
customData = data;
},
});
// custom data using hotspot name
function forwardGeocoder(query) {
var matchingFeatures = [];
for (var i = 0; i < customData.features.length; i++) {
var feature = customData.features[i];
if (feature.properties.title.toLowerCase().search(query.toLowerCase()) !== -1) {
// feature["place_name"] = '<img src="https://netmaker.io/dashboard/images/helium_logo.svg" alt="" width="15px"> ' + feature.properties.title;
feature["place_name"] = feature.properties.title;
feature["center"] = feature.geometry.coordinates;
feature["place_type"] = ["hotspot"];
matchingFeatures.push(feature);
}
}
return matchingFeatures;
}
// add the control to the map.
map.addControl(
new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
localGeocoder: forwardGeocoder,
zoom: 14,
placeholder: "Search: address or hotspot name",
mapboxgl: mapboxgl,
})
);
map.on("click", "hotspots-layer", function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.description;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
new mapboxgl.Popup().setLngLat(coordinates).setHTML(description).addTo(map);
});
map.on("mouseenter", "hotspots-layer", function () {
map.getCanvas().style.cursor = "pointer";
});
map.on("mouseleave", "hotspots-layer", function () {
map.getCanvas().style.cursor = "";
});
</script>
You're recieving an undefined because e.features[0].properties.description doesn't exist in the "hotspots-layer" data.
"features": [
{
"type": "Feature",
"properties": {
"status": "online"
},
"geometry": {
"type": "Point",
"coordinates": [
"119.73479929772",
"30.240836896893",
0
]
}
},
The only "description" in this case that you can return is the e.features[0].properties.status as seen here:
map.on("click", "hotspots-layer", function (e) {
var coordinates = e.features[0].geometry.coordinates.slice();
var description = e.features[0].properties.status;
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
}
console.log(description)
new mapboxgl.Popup().setLngLat(coordinates).setHTML(description).addTo(map);
});
This code snippet will allow the click event to show you the status when you interact with the hotspot location.

Mapbox GL JS get features within geoson boundary

Below is a complete minimal example of a dot layer on top of a choropleth layer. You simply need to add an access token and it will work. When a user clicks within one of the choropleth boundaries I would like to get a list of the points within that boundary. This is so that I can use this data to populate an element elsewhere on the page. I am aware that some boundaries overlap, but I am not worried about how this is handled.
<head>
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js"></script>
<link
href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css"
rel="stylesheet"
/>
</head>
<body>
<div id="map" style="width: 800px; height: 800px;"></div>
<script>
mapboxgl.accessToken =
"your token here";
const geojson_url =
"https://opendata.arcgis.com/datasets/d4d519d1d1a1455a9b82331228f77489_4.geojson";
fetch(geojson_url)
.then(res => res.json())
.then(geojson => {
const data = [
{ lat: "52.04009", long: "-0.52926" },
{ lat: "51.45906", long: "-2.60329" },
{ lat: "51.24257", long: "-0.58795" },
{ lat: "51.51161", long: "-0.11625" },
{ lat: "51.38113", long: "-2.36011" },
{ lat: "51.449824", long: "-2.60034" },
{ lat: "52.04009", long: "-0.52926" },
{ lat: "51.06386", long: "-2.90667" },
{ lat: "50.72623", long: "-3.5222" }
];
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/light-v10",
zoom: 5.5,
center: [-2.2, 53]
});
map.on("load", function() {
// choropleth
geojson.features = geojson.features.map(feature => {
feature["id"] = feature.properties.objectid;
return feature;
});
map.addSource("leps", {
type: "geojson",
data: geojson
});
map.addLayer({
id: "leps-fills",
type: "fill",
source: "leps",
paint: {
"fill-color": "green",
"fill-opacity": 0.5
}
});
map.on("click", "leps-fills", function(e) {
// using this event, I would like to find all the points within the clicked boundary
console.log("list of points here");
});
// point data layers
// create points geojson data
const pointMapGeojson = {
type: "FeatureCollection",
features: data.map((g, i) => {
return {
type: "Feature",
properties: g,
id: i,
geometry: {
type: "Point",
coordinates: [g.long, g.lat]
}
};
})
};
map.addSource("point_layer", {
type: "geojson",
data: pointMapGeojson
});
map.addLayer({
id: "point_layer",
type: "circle",
source: "point_layer",
layout: {
visibility: "visible"
}
});
});
});
</script>
</body>
You may look at Turf.js. It provides a function to find any points within the polygon. https://turfjs.org/docs/#pointsWithinPolygon
Here is the example:
mapboxgl.accessToken = 'pk.eyJ1IjoicGFybmRlcHUiLCJhIjoiY2l6dXZ5OXVkMDByZDMycXI2NGgyOGdyNiJ9.jyTchGQ8N1gjPdra98qRYg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-46.6062, -23.5513],
zoom: 11
});
map.on('load', function () {
var points = turf.points([
[-46.6318, -23.5523],
[-46.6246, -23.5325],
[-46.6062, -23.5513],
[-46.663, -23.554],
[-46.643, -23.557]
]);
var searchWithin = turf.polygon([[
[-46.653,-23.543],
[-46.634,-23.5346],
[-46.613,-23.543],
[-46.614,-23.559],
[-46.631,-23.567],
[-46.653,-23.560],
[-46.653,-23.543]
]]);
// Find point within polygon
var ptsWithin = turf.pointsWithinPolygon(points, searchWithin);
// Draw polygon
map.addLayer({
'id': 'searchWithin',
'type': 'fill',
'source': {
'type': 'geojson',
'data': searchWithin
},
'layout': {},
'paint': {
'fill-color': '#525252',
'fill-opacity': 0.5
}
});
// Draw all points
map.addLayer({
'id': 'points',
'type': 'circle',
'source': {
'type': 'geojson',
'data': points
},
'layout': {},
'paint': {
'circle-radius': 5,
'circle-color': 'red',
'circle-opacity': 1
}
});
// Draw points within polygon feature
map.addLayer({
'id': 'ptsWithin',
'type': 'circle',
'source': {
'type': 'geojson',
'data': ptsWithin
},
'layout': {},
'paint': {
'circle-radius': 5,
'circle-color': 'blue',
'circle-opacity': 1
}
});
});
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; };
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css' rel='stylesheet' />
<script src='https://npmcdn.com/#turf/turf/turf.min.js'></script>
<div id='map'></div>

Yandex Maps API: add multiple placemarks

I understand how to create a single placemark and if I need to create more than one I do it like that:
ymaps.ready(init);
function init() {
var map = new ymaps.Map('map', {
center: [55.76, 37.64], // lat, long
zoom: 5,
controls: ['zoomControl', 'fullscreenControl']
});
var data = [
{
name: 'Moskow',
coordinates: [55.684758, 37.738521]
},
{
name: 'Saint Petersburg',
coordinates: [59.939095, 30.315868]
},
];
for (var i = 0; i < data.length; i++) {
map.geoObjects.add(new ymaps.Placemark([data[i]['coordinates'][0], data[i]['coordinates'][1]], {
balloonContent: data[i]['name']
}));
}
}
But is there a better way to add bunch of data to the map without a cycle?
Yes, use Object Manager and organize your data into objects collection. Yandex Maps API provides convenient way for that case.
Here is the simple example according to your data:
ymaps.ready(init);
function init() {
var map = new ymaps.Map('map', {
center: [55.76, 37.64], // lat, long
zoom: 5,
controls: ['zoomControl', 'fullscreenControl']
});
// Objects collection
var collection = {
type: "FeatureCollection",
features: [
{
type: "Feature",
id: 0,
geometry: {
type: "Point",
coordinates: [55.684758, 37.738521]
},
properties: {
balloonContent: "Moskow"
}
},
{
type: "Feature",
id: 1,
geometry: {
type: "Point",
coordinates: [59.939095, 30.315868]
},
properties: {
balloonContent: "Saint Petersburg"
}
}
]
};
// Object Manager
objectManager = new ymaps.ObjectManager({ clusterize: true });
objectManager.add(collection);
map.geoObjects.add(objectManager);
}
Read about Object Manager. There you can find more examples.

How to filter a csvLayer based on attributes?

Ill start out by just explaining what I'm trying to achieve. I'm running an arcgis application based on the javascript API. I'm Rendering a CSVLayer into my SceneView like such:
require(["esri/Map", "esri/layers/CSVLayer", "esri/views/SceneView", "esri/widgets/BasemapToggle", "esri/Graphic", "esri/tasks/support/Query", "esri/widgets/Legend", "esri/widgets/Expand"],
function (Map, CSVLayer, SceneView, BasemapToggle, Graphic, Query, Legend, Expand) {
var csvLayer = new CSVLayer({
url: numberLayers[0].Url,
popupTemplate: template,
featureReduction: {
type: "selection"
}
});
csvLayer.renderer = {
type: "simple",
symbol: {
type: "simple-marker",
style: "circle",
color: [255, 255, 255, 1],
outline: { color: [0, 0, 0, 0.4] },
size: 16,
},
label: "PurpleAir Sensors",
visualVariables: [colorVisual]
};
var map = new Map({ basemap: "streets-navigation-vector", layers: csvLayer });
var view = new SceneView({
container: "divSensorMapContainer",
camera: {
position: [
-119.289320,
35.216734,
2100000,
],
},
map: map,
center: centerPoint,
constraints: {
altitude: {
min: 0,
max: 9000000,
},
},
});
What i want to do is filter this data based on the attributes defined in the CSVLayer. Ive tried doing this using the .whenLayerView function to capture the layerview and then run a .filter property to filter the dataset.
view.whenLayerView(csvLayer)
.then(function (layerView) {
sensorLayerView = layerView;
var newQuery = csvLayer.createQuery();
newQuery.where = "PM25 > '40'";
sensorLayerView.filter = newQuery;
csvLayer.queryFeatures(newQuery).then(function (result) {
if (highlight) {
highlight.remove();
}
highlight = sensorLayerView.highlight(result.features);
});
view.ui.move(["compass", "zoom", "navigation-toggle"], "bottom-right");
});
Now in this .whenLayerView function we get the layerview, define a query based on the data set, and then do two things:
Highlight sensors based on the query information.
Run a filter property on the layerView. (The Api reference information is given here:https://developers.arcgis.com/javascript//latest/api-reference/esri-views-layers-CSVLayerView.html#filter)
This is the output:
enter image description here
So does anyone know what I'm doing wrong? I used a reference tutorial to get this far, you can find that here:
https://developers.arcgis.com/javascript//latest/sample-code/featurefilter-attributes/index.html
The argument for the filter is just an object with the "where" property, not an ESRI query object.
So it should just be:
sensorLayerView.filter = {
where: "PM25 > '40'"
};
This will autocast as a new FeatureFilter

Categories

Resources