I installed Nominatim in combination with the Maplibre geocoder, but I am having troubles getting the results in the correct order of relevance or getting the correct one at all. In Italy many provinces have the same name as the capital of the province, for example Cagliari is both the name of the Province and the name of a city. When I search for
Via Roma, Cagliari
I an expecting to see the result for the street "Via Roma" located in the city of Cagliari, but I get results for "Via Roma" in other cities located in the province of Cagliari instead. This happens frequently with street names that are fairly common across cities in the same area. How can I make Nominatim to give precedence to the street located in the city with the same name or to not take the province into account at all when elaborating the query?
This is how I am calling the geocoder api
var geocoder_api = {
forwardGeocode: async (config) => {
const features = [];
try {
geocoder_bbox = map.getBounds().toArray().flat()
let request =
'https://my.server/search?q=' +
config.query +
'&format=geojson&addressdetails=1&viewbox='+geocoder_bbox;
const response = await fetch(request);
const geojson = await response.json();
for (let feature of geojson.features) {
let center = [
feature.bbox[0] +
(feature.bbox[2] - feature.bbox[0]) / 2,
feature.bbox[1] +
(feature.bbox[3] - feature.bbox[1]) / 2
];
let point = {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: center
},
place_name: feature.properties.display_name,
properties: feature.properties,
text: feature.properties.display_name,
place_type: ['place'],
center: center
};
features.push(point);
}
} catch (e) {
console.error(`Failed to forwardGeocode with error: ${e}`);
}
return {
features: features
};
}
};
Related
So I'm trying to make a function for a text RPG that allows people to click a "wander" button and end up in a random location near their current one. I've created several arrays that contain the information for which locations are near others, and I'd like to be able to press a button, generate a new location, save that location as the current location, and generate a new wander result from the new location the next time the button is pressed.
//Starting area locations
const startingAreaLocations = [Clearing, BigForrest, SmallForrest, Cliffs, Cave, Pond, Start, River, TownEnterance];
const locationsNearStart = [BigForrest, Cliffs, Cave];
const locationsNearBigForrest = [Start, Clearing, River];
const locationsNearCliffs = [Start, Cave, River];
const locationsNearCave = [Cliffs, Pond, River, Start];
const locationsNearClearing = [BigForrest, River, Start];
const locationsNearSmallForrest = [Pond, River, TownEnterance];
const locationsNearPond = [SmallForrest, Cave, River];
const locationsNearRiver = [BigForrest, Cliffs, Cave, Clearing, SmallForrest, Pond, TownEnterance];
const locationsNearTowerEnterance = [SmallForrest, River];
My issue at the moment is that when I generate and load a new location, I don't know how to tell the system which array it should be referencing for the next locations. I tried to name the variables so that I could add the location name variable to a string and come out with the array name, but even when I put it through a JSON.parse(), it reads the string, not the contents of my array.
function wander(location) {
wanderLocation = location[Math.floor(Math.random()*location.length)];
console.log(wanderLocation);
locationsNearCurrentArea = "locationsNear" + wanderLocation.name;
console.log(locationsNearCurrentArea);
callPannel(wanderLocation.string);
}
How can I better make a feature that will let me bounce between locations like this?
I would use an explicit data structure for this instead of a bunch of variables. A Map with the current location as key and nearby locations in an array as the value should suffice
const nearbyLocations = new Map([
//[ key, [ values, ... ] ],
[ Start, [ BigForrest, Cliffs, Cave ] ],
[ BigForrest, [ Start, Clearing, River ] ],
[ Cliffs, [ Start, Cave, River ] ],
// etc
])
Then you can use something like this to get a random, nearby location
const wander = (currentLocation) => {
const nearby = nearbyLocations.get(currentLocation)
return nearby?.[Math.floor(Math.random() * nearby?.length)]
}
You should put the data at least in JSON, object, or list. Here is an example using object:
let data = {
startingAreaLocations: ["Clearing", "BigForrest", "SmallForrest"],
locationsNearStart: ["BigForrest", "Cliffs", "Cave"],
locationsNearBigForrest: ["Start", "Clearing", "River"],
};
let newLocation = "River";
let newWander = ["SmallForrest", "BigForrest"];
let dataKey = Object.values(data);
data[Object.keys(data)[Object.keys(data).length - 1]].forEach((e) => {
if (e === newLocation) {
data[`locationsNear${e}`] = newWander;
}
});
console.log(data);
I am trying to take two pieces of data from an object and push it as a new object into an array. The data is being supplied by an API call to my SQL database. That API call is working correctly and displaying the object in a console table. When the script runs a forEach method to extract the data into its own object and then push that new object to a new array, the new array returns "undefined". Code below:
Example data (only one placeholder entry currently, the events array will be seeded with multiple examples in this format)
events = [{location: "Emergency Shelter", latitude: "37.5434", longitude: "-77.4435"}]
Empty arrays declared and API call functioning properly:
let events = [];
let locations = [];
$.get("/api/events", data => {
for (let i = 0; i < data.length; i++) {
events.push(data[i]);
}
});
console.table displays the object correctly and includes the keys "latitude" and "longitude" with the correct corresponding values
forEach method:
locations = events.forEach(location => {
const coords = {};
coords.latitude = location.latitude;
coords.longitude = location.longitude;
locations.push(coords);
});
console.log("Coordinates list: " + locations);
console.log displays "Coordinates list: undefined"
I feel like I may be missing a return somewhere, but I'm not sure where. I tried adding
return locations;
inside the forEach method but it doesn't change anything (and I believe that would exit my function prior to getting through the entire array). Any help is appreciated!
forEach returns nothing so locations should be undefined. You shouldn't pass return value of forEach to locations
events.forEach(location => {
const coords = {};
coords.latitude = location.latitude;
coords.longitude = location.longitude;
locations.push(coords);
});
console.log("Coordinates list: " + locations);
Also you can use map function.
const events = [
{ location: 'Emergency Shelter', latitude: '37.5434', longitude: '-77.4435' }
];
const locations = events.map(({ latitude, longitude }) => ({
latitude,
longitude
}));
console.log(locations);
Try a map based approach which also would condense your code to ...
const events = [{
location: "Emergency Shelter",
latitude: "37.5434",
longitude: "-77.4435"
}, {
location: "Peopl's Kitchen",
latitude: "36",
longitude: "-78"
}, {
location: "Salvation Army",
latitude: "38",
longitude: "-76"
}];
const locations = events.map(({ latitude, longitude }) => ({ latitude, longitude }));
console.log("Coordinates list: ", locations); // do not concatenate the result.
console.log("Coordinates list: " + locations);
console.log("Coordinates list: " + JSON.stringify(locations));
.as-console-wrapper { min-height: 100%!important; top: 0; }
map creates a new array by iterating another one where each item of the new array equals the return value of the mapping/transforming function which, at each iteration step, does process the current value of the original array.
I'm trying to perform a simple ST_Dwithin search using sequelize.js and PostGIS.
In my database I have 3 tables of interest: Users, Neighborhoods and Addresses. All geo data is stored inside addresses table, which has references to users and neighborhoods.
return Neighborhood.findById(id).then(neighborhood => {
return neighborhood.getAddress().then(address => {
return Address.findAll({
where: sequelize.where(
sequelize.fn(
'ST_DWithin',
sequelize.fn(
'ST_Transform',
address.position,
26986),
sequelize.fn('ST_Transform',
sequelize.col('position'),
26986),
distance),
true
)
})
})
}).catch(err => new Error(err));
First I get the address of a neighborhood and then use sequelize.fn to query with PostGIS ST_DWithin function. However this throws an error TypeError: val.replace is not a function. I believe it is something with line address.position. The column position in the table Addresses stores geometry points with type GEOMETRY and srid 4326.
The function works correctly if instead of address.position I hard code something like sequelize.fn('ST_GeomFromText', 'POINT(39.807222 -76.984722)', 4326)
In my case, the geometry(point) attribute is in the User entity. This is what I got working:
var lat = parseFloat(json.lat);
var lng = parseFloat(json.lng);
var attributes = Object.keys(User.attributes);
var distanceAttribute =
sequelize.fn('ST_Distance_Sphere',
sequelize.literal('geolocation'),
sequelize.literal('ST_MakePoint('+lat+','+lng+')'));
var distanceAlias = [distanceAttribute, 'distance'];
attributes.push(distanceAlias);
var query = {
attributes: attributes,
where: sequelize.where(distanceAttribute, {$lte: 100000}),
logging: console.log
}
User.findAll(query)
.then(function(instance){
console.log(instance);
});
Which produces a SQL like this:
SELECT "user_id", "user_name" ... ST_Distance_Sphere(geolocation,
ST_MakePoint(-22.4149023,-47.56513940000002)) AS "distance"
FROM "user" AS "User"
WHERE ST_Distance_Sphere(geolocation,
ST_MakePoint(-22.4149023,-47.56513940000002)) <= 100000;
I think this should work for you too by changing User to Address
Using the Google Maps Geocoding API, i'm able to get the formatted address for a particular coordinate. To get the exact city name, I'm doing the following:
$.ajax({
url: 'http://maps.googleapis.com/maps/api/geocode/json?latlng='+lat+','+long+'&sensor=false',
success: function(data){
var formatted = data.results;
var address_array = formatted[6].formatted_address.split(',');
var city = address_array[0];
}
});
where lat and long are derived using the browser coordinates. My problem is the following:
From coordinates 19.2100 and 72.1800, I get the city as Mumbai, but from a similar set of coordinates about 3Km away, I get city as Mumbai Suburban. How can I get Mumbai without changing the success function of my code? It seems to me that the result array doesn't always stick to the same format which creates problems in my displaying of the city name.
So I was trying to figure this out today and I came to this solution if it helps anyone. Google maps comes with the Geocoder built in now so you just create a geocoder object once the API has loaded.
You can easily wrap that in a function and just return an object with the city, state, and postal code. This site was helpful in allowing me to see what the different 'types' mean: Reverse Geocoding
var geocoder = new google.maps.Geocoder,
latitude = 28.54, //sub in your latitude
longitude = -81.39, //sub in your longitude
postal_code,
city,
state;
geocoder.geocode({'location': {lat:latitude, lng:longitude}}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
results.forEach(function(element){
element.address_components.forEach(function(element2){
element2.types.forEach(function(element3){
switch(element3){
case 'postal_code':
postal_code = element2.long_name;
break;
case 'administrative_area_level_1':
state = element2.long_name;
break;
case 'locality':
city = element2.long_name;
break;
}
})
});
});
}
});
You need to look at the type of the result, not the absolute index in the array of results. Iterate through the results array looking for the entry which has the appropriate type. Looks like that would be:
locality indicates an incorporated city or town political entity
But data may vary with region.
related question: Grabbing country from google geocode jquery
Looks like you want the entry with both the 'locality' and the 'political' types:
{
"long_name" : "Mumbai",
"short_name" : "Mumbai",
"types" : [ "locality", "political" ]
}
For what it's worth, I was looking for something similar and am trying https://plus.codes/
If you strip the encoded bit it yields a fairly consistent city, state, country name:
const extractCityName = latlng => {
googleMapsClient.reverseGeocode({ latlng }, (err, response) => {
if (!err) {
return response.json.plus_code.compound_code.split(' ').slice(1).join(' ');
}
});
};
// examples:
console.log(extractCityName(40.6599718,-73.9817292));
// New York, NY, USA
console.log(extractCityName(37.386052, -122.083851));
// Mountain View, CA, USA
console.log(extractCityName(51.507351, -0.127758));
// Westminster, London, UK
$.get({
url: locAPI,
success: function(data)
{
data.results[0].address_components.forEach(function(element){
// console.log(element.types);
if(element.types[0] == 'locality' && element.types[1] == 'political')
{
console.log('City:')
console.log(element.long_name);
}
if(element.types[0] == 'country' && element.types[1] == 'political')
{
console.log('Country:')
console.log(element.long_name);
}
});
}
})
In My Case, I had to find City and Country Name.. That's what I did.
I JSON nodes with the same title, but different latitude and longitude values in each node. I need to check for the same title value, but then merge together the latitude and longitude values into a url for a map API. I need it to be in this order latitude, longitude, latitude, longitude, etc... I just don't know what to do at this point. Thanks for any help or suggestions.
JS VAR
<img class="detail_map" src="http://open.mapquestapi.com/staticmap/v4/getplacemap?size=320,240&zoom=15&location=' + data.nodes.Latitude + ',' + data.nodes.Longitude + '&imagetype=jpeg&showicon=blue-1">
JSON Object
var data = fl({
"nodes":[
{"node":{
"title":"180","Address":"555 Market St. San Francisco, CA United States See map: Google Maps","
Latitude":"37.789952","
Longitude":"-122.400158"}},
{"node":{
"title":"180","Address":"Epic Roasthouse (399 Embarcadero) San Francisco, CA United States See map: Google Maps","
Latitude":"37.797677","
Longitude":"-122.394339"}},
{"node":{
"title":"180","Address":"Mason & California Streets (Nob Hill) San Francisco, CA United States See map: Google Maps","
Latitude":"37.791556","
Longitude":"-122.410766"}},
{"node":{
"title":"180","Address":"Justin Herman Plaza San Francisco, CA United States See map: Google Maps","
Latitude":"37.774930","
Longitude":"-122.419416"}},
{"node":{
"title":"180","Address":"200 block Market Street San Francisco, CA United States See map: Google Maps","
Latitude":"37.793133","
Longitude":"-122.396560"}}
]});
});
You can use $.extend() function for merge two objects, the different properties will be replaced.
Example:
var newData = $.extend(data1, data2);
// Assuming data1, data2 are objects;
If you need convert string JSON to object, use $.parseJSON()
you can try to do something like this where var url_part is used to replace "data.nodes.Latitude + ',' + data.nodes.Longitude" from the img src:
var url_part = '';
$.each(data.nodes, function(key,val){
url_part += val.node.Latitude+","+val.node.Longitude+",";
});
but before useding url_part, you need to remove the last coma...
Use jQuery's $.map(). Compare the object's node.title value, and then from there return the string as you would expect. When you're done, join the array on ,.
var locs = $.map(fl.nodes, function(obj,i){
return obj.node.title == '180' ? 'latitude='+obj.node.Latitude+'&longitude='+obj.node.Longitude : '';
}).join(',');
would return lat1,long1,lat2,long2,lat3,long3,lat4,long4,lat5,long5
Working jsFiddle
Edit 1
Added support for dynamic titles.
function getNodesByTitle(title){
return $.map(fl.nodes, function(obj,i){
return obj.node.title == title ? 'latitude='+obj.node.Latitude+'&longitude='+obj.node.Longitude : '';
}).join(',');
}
In theoretical practice:
var locString = getNodesByTitle('your title here');
I had to create a function to merge addresses and latitude and longitude. Check out the jsFiddle to see it in action.
function fl(data){
//data = JSON.parse(data);
//Array to hold movie titles
var movieTitles = [];
//Assign Movies to to movies Array and ensure unique
for (var i=0; i < data.nodes.length; i++)
{
//Look for the current title in the movieTitles array
var movieIndex = movieTitles.indexOf(data.nodes[i].node.title);
if (movieIndex>-1) //If the title already exists
{
//Merge all the properties you want here
movies[movieIndex].Address += ", " + data.nodes[i].node.Address;
if(!movies[movieIndex].Coords) movies[movieIndex].Coords = [];
movies[movieIndex].Coords.push(
data.nodes[i].node.Latitude + "," + data.nodes[i].node.Longitude
);
}
else
{
//var address = movies[movieIndex].Address; movies[movieIndex].Address = address.replace(/^\s*/,'');
//Add movie to movies array
movies.push(data.nodes[i].node);
//Add movie title to movieTitles array
movieTitles.push(data.nodes[i].node.title);
}
}
displayLinks(); //Load all the links
//showCast(0); //Display details for first item
}
//});