Geocoding Openstreetmap gives different responses depending on zoom-level - api

I am trying to figure out why when hoovering on a village when zoom level is low show's the town of Västerås instead of Irsta, but when I zoom in it shows the village of Irsta.
You guys can try it yourselves in this example.
I would like to be able to locate Irsta when you can see the label of Irsta, it shouldn't be the bigger city of Västerås in that case.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Query Nominatem</title>
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.8.0/dist/leaflet.css" integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.8.0/dist/leaflet.js" integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ==" crossorigin=""></script>
<style>
html, body, #map {
height:100%;
width:100%;
padding:0px;
margin:0px;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const map = L.map('map').setView([48.210033, 16.363449], 10);
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
}).addTo(map);
let timeOut;
let popUp;
let queryState = false;
function mousemove(e) {
clearTimeout(timeOut)
if (!queryState) {
timeOut = setTimeout(() => {
queryState = true;
if (popUp) {
popUp.remove()
}
popPup = L.popup()
.setLatLng(e.latlng)
.setContent("Loading data....")
.openOn(map);
queryNominatem(e)
}, 1000);
}
}
function queryNominatem(e) {
fetch(`https://nominatim.openstreetmap.org/reverse.php?lat=${e.latlng.lat}&lon=${e.latlng.lng}&zoom=${map.getZoom()}&format=json`).then(
(x) => {
return x.json()
}
).then(
(data) => {
popUp = L.popup()
.setLatLng(e.latlng)
.setContent(data.display_name)
.openOn(map);
queryState = false;
}
).catch(
(err) => {
console.log(err);
popUp = L.popup()
.setLatLng(e.latlng)
.setContent("Error Loading data ... ")
.openOn(map);
setTimeout(() => {
popUp.remove();
queryState = false;
}, 1000);
}
);
}
map.on("mousemove", mousemove);
</script>
</body>
</html>
Zoom-in picture of village Irsta
Normal zooom of village Irsta

This is happening as you are using a dynamic zoom level in your reverse Nominatim request. If the zoom level is lower than 14, Nominatim will try to get the next city, (or if it way lower, even the country, etc., see https://nominatim.org/release-docs/develop/api/Reverse/#result-limitation )
So you'll have to define a min-value in your zoom while doing the reverse call, e.g. q&d:
reverseZoom=Math.max(14,map.getZoom());
fetch(`https://nominatim.openstreetmap.org/reverse.php?lat=${e.latlng.lat}&lon=${e.latlng.lng}&zoom=${reverseZoom}&format=json`)
Now the reverse call will always try to get a village, but - and this may be a problem - also a suburb instead of a city (but this would have happened with your original code as well if you zoom in). So I would recommend to use addressdetails=1 in your request and parse the addressdetails response for city/village/town to return that value instead of the display name value.

Related

How to get data out of a popup on a map with a feature layer from ArcGIS-api for javascript and reuse that data?

I am using a map with feature layers from ArcGIS and the popup to see some data when the user click on a symbol (feature). Is there a way to style the popups exactly the way we want ? In other words, how could I get the data out of the popup, not display the popup but open a modal with that data instead ? Is that even possible ?
This are actually two questions, can you style the popup?, and can you use your own "popup"?.
For the first one, I would say it is pretty customizable, but obviously it depends what you need.
For the second, you just need to stop the default behavior of the view popup, that is to open on right click, and then catch the event yourself to do what you want. Here is an example I made for you that shows that,
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
<title>PopupTemplate - Auto Open False</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.21/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.21/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 500px;
width: 100%;
}
#features {
margin: 20px;
height: 500px;
width: 100%;
overflow: auto;
}
</style>
<script>
var populationChange;
require(["esri/Map", "esri/views/MapView", "esri/layers/Layer"], function (
Map,
MapView,
Layer
) {
const map = new Map({
basemap: "dark-gray"
});
const view = new MapView({
container: "viewDiv",
map: map,
zoom: 7,
center: [-87, 34]
});
Layer.fromPortalItem({
portalItem: {
id: "e8f85b4982a24210b9c8aa20ba4e1bf7"
}
}).then(function (layer) {
map.add(layer);
view.popup.autoOpenEnabled = false; // <- disable view popup auto open
view.on("click", function (event) { // <- listen to view click event
if (event.button === 0) { // <- check that was left button or touch
view.whenLayerView(layer).then(function (layerView) {
const query = layerView.layer.createQuery();
query.geometry = view.toMap(event);
query.distance = 1;
query.units = "meters";
layerView.queryFeatures(query).then(
response => {
document.getElementById("features").innerText = JSON.stringify(response.features);
console.error(response);
},
err => {
document.getElementById("features").innerText = "Query returns an error, check console to see what happen!.";
console.error(err);
}
);
});
}
});
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
<div id="features"></div>
</body>
</html>

arcGIS 4.18 locate widget custom icon

I have to change the default icon on the Locate widget on arcGIS 4.18. The default icon class is, esri-icon-locate how can I change it to the class, 'esri-icon-navigation'?
I am going through the documentation,
https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Locate.html#iconClass
I have tried to use the property, 'iconClass'. But not reflecting in the map icon. Please find the code below,
var locateBtn = new Locate({
view: view,
// iconClass: '\ue666'
iconClass: 'esri-icon-navigation'
});
view.ui.add(locateBtn, {
position: "manual",
});
KER,
You actually right, does not work as expected. Setting iconClass should be the solution.
Funny fact if you check the default iconClass is actually esri-icon-north-navigation, which obviously in not.
Anyway, I am gonna give a dirty solution, just overlap the class you want,
view.when(_ => {
const n = document.getElementsByClassName("esri-icon-locate");
if (n && n.length === 1) {
n[0].classList += " esri-icon-navigation"
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
<title>Locate button | Sample | ArcGIS API for JavaScript 4.18</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.18/esri/themes/light/main.css" />
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<script src="https://js.arcgis.com/4.18/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/Locate"
], function (Map, MapView, Locate) {
var map = new Map({
basemap: "topo-vector"
});
var view = new MapView({
container: "viewDiv",
map: map,
center: [-56.049, 38.485, 78],
zoom: 3
});
var locateBtn = new Locate({
view: view
});
// Add the locate widget to the top left corner of the view
view.ui.add(locateBtn, {
position: "top-left"
});
view.when(_ => {
const n = document.getElementsByClassName("esri-icon-locate");
if (n && n.length === 1) {
n[0].classList += " esri-icon-navigation"
}
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>

Limit map area/zoom in arcGIS SceneView

I'm using arcGIS SceneView in local viewing mode to display a WebMap. I'm trying to constrain the zoom level and bounds of an area so that the user can only see the US, Hawaii, and Alaska and cannot pan outside of this. I also need to constrain the zoom level because if the user zooms out too far the over-zoom the map and see untiled/unmapped space.
Are there any potential solves for this? I first thought that using the constraints property might solve it, but it appears the properties that can be fed to this are quite limited:
https://developers.arcgis.com/javascript/latest/api-reference/esri-views-SceneView.html#constraints
One way to achieve what I think you want is:
listen to view property changes,
check constraints,
act accordingly.
Take a look a the example I made for you. In it, I wait for the view to stop updating (updating property), then I check the constraints. If it is out of scale or out of the extent I reset the view. You probably want another action, I just made it simple.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>
View constraints
</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.17/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.17/"></script>
<script>
require([
"esri/Map",
"esri/views/SceneView",
"esri/geometry/Extent"
], function (Map, SceneView, Extent) {
const extent = Extent.fromJSON(
{
"spatialReference": {
"latestWkid":3857,
"wkid":102100
},
"xmin":-13119716.983985346,
"ymin":4024337.3961656773,
"xmax":-13096023.097830579,
"ymax":4030581.302795334
}
);
const MIN_SCALE = 12000;
const MAX_SCALE = 48000;
const map = new Map({
basemap: "topo-vector",
ground: "world-elevation"
});
const view = new SceneView({
container: "viewDiv",
map: map,
viewingMode: "local",
center: [-117.75, 33.99],
scale: 24000
});
function resetView() {
console.log('reset');
view.goTo(
{
center:[-117.75, 33.99],
scale: 24000
}
);
}
view.watch("updating", function (value) {
if (!value) {
if (
// out of scale
view.scale < MIN_SCALE ||
view.scale > MAX_SCALE ||
// out of extent
view.extent.xmin < extent.xmin ||
extent.xmax < view.extent.xmax ||
view.extent.ymin < extent.ymin ||
extent.ymax < view.extent.ymax
) {
resetView();
};
}
});
});
</script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>

How to get the coordinates of point where I clicked while using Select interaction?

We use Vue.js and OpenLayers (4.6.5) in our web project. We have a lot of features on the map and some of them are polygons. When I select some particular polygon, its style turns to another color, which means it's highlighted (selected). Of course, I can get the coordinates of selected polygon. But, how can I get the coordinates of point inside that polygon where I clicked?
The code look as following:
markObject (mark) {
if (!mark) {
this.map.un('select', this.onMarkObject)
if (this.markSelection) {
this.markSelection.getFeatures().remove(this.lastSelectedFeature)
this.map.removeInteraction(this.markSelection)
}
return
}
if (!this.markSelection) {
this.markSelection = new Select({
condition: condition.click,
layers: [this.vectorLayer]
})
this.markSelection.on('select', this.onMarkObject)
}
this.map.addInteraction(this.markSelection)
},
onMarkObject (event) {
if (event.selected && event.selected.length > 0) {
const coordinates = event.selected[0].getGeometry().getCoordinates()
}
}
Actually, I've found the solution:
onMarkObject (event) {
const clickCoordinates = event.mapBrowserEvent.coordinate
...
}
Thank you anyway.
What you need is to capture the click event on the map, and then transform pixel to map coordinates, take a look at this example I made for you,
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.1.1/css/ol.css" type="text/css">
<style>
.map {
height: 400px;
width: 100%;
}
#a { display: none; }
</style>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.1.1/build/ol.js"></script>
<title>Click Pixel Coord</title>
</head>
<body>
<h2>Click on Map to get pixel and coord values</h2>
<p id="p"></p>
<div id="map" class="map"></div>
<script type="text/javascript">
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
center: ol.proj.fromLonLat([37.41, 8.82]),
zoom: 4
})
});
map.on('click', function(evt) {
const coord = map.getCoordinateFromPixel(evt.pixel);
document.getElementById('p').innerHTML =
`Pixel:${evt.pixel[0]} ${evt.pixel[0]}` + '<br>' +
`Coord:${coord[0].toFixed(2)} ${coord[1].toFixed(2)}`;
});
</script>
</body>
</html>

Calculate Esri map extents from gps coordinates

I add some markers on Esri map and want to show map at zoom level where all markers are visible. I calculated minimum and maximum lat-lng and set the extent. But its not working.
Given Coordinates
lat, lon:41.984440, -87.827278
lat, lon:41.874489, -87.705772
calculated min-max:
xMax:"-87.827278"
xMin:"-87.705772"
yMax:"41.984440"
yMin:"41.874489"
expected result:
> EsriSetMapExtent:function(obj)
> {
> var extent = new esri.geometry.Extent(obj.xMin, obj.yMin, obj.xMax, obj.yMax);
> m.esriMap.setExtent(extent);
> },
The values of xMin and xMax are reversed., Only the X-axis, please check the function which performs the calculations. If you change them it will work. Below is the working sample
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
<title>Simple Map</title>
<link rel="stylesheet" href="https://js.arcgis.com/3.17/esri/css/esri.css">
<style>
html, body, #map {
height: 100%;
margin: 0;
padding: 0;
}
</style>
<script src="https://js.arcgis.com/3.17/"></script>
<script>
var map;
require(["esri/map","esri/geometry/Extent", "esri/SpatialReference", "esri/geometry/Point", "esri/symbols/SimpleMarkerSymbol",
"esri/Color", "esri/graphic", "dojo/domReady!"], function(Map, Extent, SpatialReference, Point, SimpleMarkerSymbol, Color, Graphic) {
map = new Map("map", {
basemap: "topo", //For full list of pre-defined basemaps, navigate to http://arcg.is/1JVo6Wd
center: [-87.705772, 41.874489], // longitude, latitude
zoom: 13
});
map.on('load', function(evt){
var pt = new Point(-87.705772, 41.874489, new SpatialReference({wkid:4326}))
var sms = new SimpleMarkerSymbol().setStyle(SimpleMarkerSymbol.STYLE_SQUARE).setColor(
new Color([255,0,0,0.5]));
var graphic = new Graphic(pt,sms);
map.graphics.add(graphic);
pt = new Point(-87.827278, 41.984440, new SpatialReference({wkid:4326}))
sms = new SimpleMarkerSymbol().setStyle(SimpleMarkerSymbol.STYLE_SQUARE).setColor(
new Color([0,255,0,0.5]));
graphic = new Graphic(pt,sms);
map.graphics.add(graphic);
})
var extent = new Extent(-87.827278, 41.874489, -87.705772, 41.984440, new SpatialReference({ wkid:4326 }));
map.setExtent(extent, true);
});
</script>
</head>
<body>
<div id="map"></div>
</body>
</html>
The solution that worked for me was:
const geoPoint = new Point(Longitude, Latitude);
if (!map.extent.contains(geoPoint)) {
// point not in view
}