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
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 = '';
});
});
})();
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.
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>
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.
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