Why does geocoding let me choose an exact address and then once clicked show different city name? - geocoding

This question concerns a variation on the Try It Yourself section on the Google Maps Place Autocomplete Address Sample, from which you can get a Fiddle with a demo API key.
The problem is I thought I had it working perfectly just now after spending two hours trying to get it to work the way I want. When I typed an address where I used to live, the autosuggest suggested my address, 82 hopkins rd, northfield, ct, then when I click the result it fills in my town as being Litchfield, CT, which is actually the larger town one town over, and also the same name of the County that Northfield is in. Google obviously knows about Northfield though it is a tiny town, because it shows the exact address I want but when I click it why would it show Litchfield? If I try some other towns, etc. I don't see this happening. I imagine there is no solution because when I tried to change:
{types: ['geocode']}); to {types: ['(cities)']}); and tried typing in Northfield, then it showed other states with the town Northfield but not Connecticut. So I guess my old town is too small for Google to care about but then why show it in the autosuggest? Strange...
// This example displays an address form, using the autocomplete feature
// of the Google Places API to help users fill in the information.
// This example requires the Places library. Include the libraries=places
// parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">
var placeSearch, autocomplete;
var componentForm = {
locality: 'short_name',
administrative_area_level_1: 'short_name',
};
function initAutocomplete() {
// Create the autocomplete object, restricting the search to geographical
// location types.
autocomplete = new google.maps.places.Autocomplete(
/** #type {!HTMLInputElement} */(document.getElementById('autocomplete')),
{types: ['geocode']});
// When the user selects an address from the dropdown, populate the address
// fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
for (var component in componentForm) {
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
// Get each component of the address from the place details
// and fill the corresponding field on the form.
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentForm[addressType]) {
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}lat = place.geometry.location.lat();
lng = place.geometry.location.lng();
document.getElementById('lat').value = lat;
document.getElementById('lon').value = lng;
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
autocomplete.setBounds(circle.getBounds());
});
}
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=NOTMYREALKEYHERE&libraries=places&callback=initAutocomplete"
async defer></script>

You're seeing these results because Northfield is not a city. According to Wikipedia, Northfield, CT is "an unincorporated village in the town of Litchfield, Litchfield County, Connecticut". However, you're in luck: The example that you're viewing is using the API output address_component, where formatted_address for your place is correctly listed as 82 Hopkins Rd, Northfield, CT 06778, USA.
From the "Geocoding Address Types section" in the Google Maps Javascript API documentation, there are a wide variety of types of geocoding result. Some, like locality, require incoporation; sublocality_level_1 through sublocality_level_5 refer to areas narrower than that.
Even without your Javascript, you can do a search:
https://maps.googleapis.com/maps/api/place/findplacefromtext/json?key=YOUR_KEY&inputtype=textquery&input=82+hopkins+rd,+northfield,+ct
...to get a place ID of ChIJZYpAVT2W54kRPnm9uVA3cpc, and then you can do a subsequent place search:
https://maps.googleapis.com/maps/api/place/details/json?key=YOUR_KEY&place_id=ChIJZYpAVT2W54kRPnm9uVA3cpc
...to reveal the different types of address component:
"address_components" : [
{
"long_name" : "82",
"short_name" : "82",
"types" : [ "street_number" ]
},
{
"long_name" : "Hopkins Road",
"short_name" : "Hopkins Rd",
"types" : [ "route" ]
},
{
"long_name" : "Northfield",
"short_name" : "Northfield",
"types" : [ "neighborhood", "political" ]
},
{
"long_name" : "Litchfield",
"short_name" : "Litchfield",
"types" : [ "locality", "political" ]
},
{
"long_name" : "Litchfield",
"short_name" : "Litchfield",
"types" : [ "administrative_area_level_3", "political" ]
},
{
"long_name" : "Litchfield County",
"short_name" : "Litchfield County",
"types" : [ "administrative_area_level_2", "political" ]
},
{
"long_name" : "Connecticut",
"short_name" : "CT",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "United States",
"short_name" : "US",
"types" : [ "country", "political" ]
},
{
"long_name" : "06778",
"short_name" : "06778",
"types" : [ "postal_code" ]
}
To your point, this also gives a formatted_address of 82 Hopkins Rd, Northfield, CT 06778, USA, which is what you're looking for.
In the example you quoted, which is the "try it yourself" section on the Place Autocomplete Address Form sample, there are two separate processes happening: The Autocomplete searches for your address and suggests a formatted address, and then the place details call unpacks the structured pieces of the address as needed. However, as in the code comments:
<!-- Note: Selection of address components in this example is typical.
You may need to adjust it for the locations relevant to your app. See
https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete-addressform
-->
Thus, by tweaking the form as I have in this Fiddle, you can see that your formatted address includes Northfield, listed as a Neighborhood, even though the city is properly Litchfield. Note that this is also location-specific behavior, as "1600 Pennsylvania Avenue, Washington DC" returns a Neighborhood of "Northwest Washington" is omitted from the formatted address.
Thus, to get the behavior you want, just use the formatted_address, or consider expanding the address_components to provide the granularity you need.

Related

MongoDB query to find text in third level array of objects

I have a Mongo collection that contains data on saved searches in a Vue/Laravel app, and it contains records like the following:
{
"_id" : ObjectId("6202f3357a02e8740039f343"),
"q" : null,
"name" : "FCA last 3 years",
"frequency" : "Daily",
"scope" : "FederalContractAwardModel",
"filters" : {
"condition" : "AND",
"rules" : [
{
"id" : "awardDate",
"operator" : "between_relative_backward",
"value" : [
"now-3.5y/d",
"now/d"
]
},
{
"id" : "subtypes.extentCompeted",
"operator" : "in",
"value" : [
"Full and Open Competition"
]
}
]
},
The problem is the value in the item in the rules array that has the decimal.
"value" : [
"now-3.5y/d",
"now/d"
]
in particular the decimal. Because of a UI error, the user was allowed to enter a decimal value, and so this needs to be fixed to remove the decimal like so.
"value" : [
"now-3y/d",
"now/d"
]
My problem is writing a Mongo query to identify these records (I'm a Mongo noob). What I need is to identify records in this collection that have an item in the filters.rules array with an item in the 'value` array that contains a decimal.
Piece of cake, right?
Here's as far as I've gotten.
myCollection.find({"filters.rules": })
but I'm not sure where to go from here.
UPDATE: After running the regex provided by #R2D2, I found that it also brings up records with a valid date string , e.g.
"rules" : [
{
"id" : "dueDate",
"operator" : "between",
"value" : [
"2018-09-10T19:04:00.000Z",
null
]
},
so what I need to do is filter out cases where the period has a double 0 on either side (i.e. 00.00). If I read the regex correctly, this part
[^\.]
is excluding characters, so I would want something like
[^00\.00]
but running this query
db.collection.find( {
"filters.rules.value": { $regex: /\.[^00\.00]*/ }
} )
still returns the same records, even though it works as expected in a regex tester. What am I missing?
To find all documents containing at least one value string with (.) , try:
db.collection.find( {
"filters.rules.value": { $regex: /\.[^\.]*/ }
} )
Or you can filter only the fields that need fix via aggregation as follow:
[direct: mongos]> db.tes.aggregate([ {$unwind:"$filters.rules"}, {$unwind:"$filters.rules.value"}, {$match:{ "filters.rules.value": {$regex: /\.[^\.]*/ } }} ,{$project:{_id:1,oldValue:"$filters.rules.value"}} ])
[
{ _id: ObjectId("6202f3357a02e8740039f343"), oldValue: 'now-3.5y/d' }
]
[direct: mongos]>
Later to update those values:
db.collection.update({
"filters.rules.value": "now-3.5y/d"
},
{
$set: {
"filters.rules.$[x].value.$": "now-3,5y/d-CORRECTED"
}
},
{
arrayFilters: [
{
"x.value": "now-3.5y/d"
}
]
})
playground

Google Charts - Area Charts with Curved (Smooth) Lines

I am trying to create an area chart in Google Charts but I want the lines to be curved instead of sharp. The option curveType: function seems to work only in line charts. Anyone cracked this earlier?
I think the reference to using intervals may be talking about something like the following. I'm giving JSON versions of options and data here. The important bits are the options "curveType" and "intervals" (which will set the color of the area under the curve and specify that the interval should be an area), and the two additional columns in the data which define the bottom and top of the interval. Set the bottom of the interval equal to the value at the bottom of the graph (0 in my case), and the top of the interval equal to the data point.
"options" : {
"vAxis" : { "title" : "No. of Results", "titleTextStyle" : { "italic" : false} },
"series": [{"color" : "#9a5324"}],
"curveType" : "function",
"intervals" : { "style" : "area", "color" : "#D49464" },
"legend" : { "position" : "none" },
"height" : 320,
"width" : 355
}
"data" : [
[ "Month", "NumResults", { "role" : "annotation" }, { "id" : "iBottom", "type" : "number", "role" : "interval" }, { "id" : "iTop", "type" : "number", "role": "interval" } ],
[ "Sep-2013",1000, "1000",0,1000 ],
[ "Oct-2013",1550, "1550",0,1550 ],
[ "Nov-2013",900,"900", 0,900 ],
[ "Dec-2013",400,"400",0,400 ]]
There is an issue from 2015 on the subject. The first comment states:
Until then, if you want to work at it, it turns out that you could make a
smoothed AreaChart using the 'interval' role, combining multiple intervals
with styles of area and line. You'll have to add an extra series just so
the intervals can be associated with the right domain values. See the
details about intervals at https://developers.google.com/chart/interactive/docs/gallery/intervals#area-intervals

Rails, Highchart maps - adding custom data

I need some basic assistance with a Highmap (via Highcharts) I am trying to put in my Rails 4 app. I suspect I have some fundamental misunderstanding of it but can't find any clear guidance.
See a simple fiddle taken from the documentation, here
http://jsfiddle.net/SimonWalsh/zpdc1btu/
What I ultimately need to do is provide membership numbers for each country so that it will be displayed much the same as the population density is in this map.
I know I need to provide my data and the means to join it to the map data in
series : [{
data : data,
mapData: Highcharts.maps['custom/world'],
joinBy: ['iso-a2', 'code'],
name: 'Population density',
states: {
hover: {
color: '#BADA55'
}
}
}]
In this example, I am guessing that the data is being pulled from an external source and that the map data is the 'iso-a2' part of the array.
If this is the case, then why can't I supply this with my data....as an example see the added array with my data.....(just one example given for Denmark)
var mydata = [
{
"iso-a2": "dk",
"value": 30
},
]
and then do
series : [{
data : mydata,
mapData: Highcharts.maps['custom/world'],
joinBy: ['iso-a2', 'value'],
name: 'Population density',
states: {
hover: {
color: '#BADA55'
}
}
}]
This does not work.....any guidance at all (other than simply pointing me to docs would be greatly appreciated)
The joinBy specifies on which value you map a country with your data. With
joinBy: ['iso-a2', 'code']
you say that the 'iso-a2' value of the mapData should be equal to the 'code' value of your data. Therefore, your data must have this format:
var mydata = [
{
"code": "dk",
"value": 30
},
/* ... */
]

Implement auto-complete feature using MongoDB search

I have a MongoDB collection of documents of the form
{
"id": 42,
"title": "candy can",
"description": "canada candy canteen",
"brand": "cannister candid",
"manufacturer": "candle canvas"
}
I need to implement auto-complete feature based on the input search term by matching in the fields except id. For example, if the input term is can, then I should return all matching words in the document as
{ hints: ["candy", "can", "canada", "canteen", ...]
I looked at this question but it didn't help. I also tried searching how to do regex search in multiple fields and extract matching tokens, or extracting matching tokens in a MongoDB text search but couldn't find any help.
tl;dr
There is no easy solution for what you want, since normal queries can't modify the fields they return. There is a solution (using the below mapReduce inline instead of doing an output to a collection), but except for very small databases, it is not possible to do this in realtime.
The problem
As written, a normal query can't really modify the fields it returns. But there are other problems. If you want to do a regex search in halfway decent time, you would have to index all fields, which would need a disproportional amount of RAM for that feature. If you wouldn't index all fields, a regex search would cause a collection scan, which means that every document would have to be loaded from disk, which would take too much time for autocompletion to be convenient. Furthermore, multiple simultaneous users requesting autocompletion would create considerable load on the backend.
The solution
The problem is quite similar to one I have already answered: We need to extract every word out of multiple fields, remove the stop words and save the remaining words together with a link to the respective document(s) the word was found in a collection. Now, for getting an autocompletion list, we simply query the indexed word list.
Step 1: Use a map/reduce job to extract the words
db.yourCollection.mapReduce(
// Map function
function() {
// We need to save this in a local var as per scoping problems
var document = this;
// You need to expand this according to your needs
var stopwords = ["the","this","and","or"];
for(var prop in document) {
// We are only interested in strings and explicitly not in _id
if(prop === "_id" || typeof document[prop] !== 'string') {
continue
}
(document[prop]).split(" ").forEach(
function(word){
// You might want to adjust this to your needs
var cleaned = word.replace(/[;,.]/g,"")
if(
// We neither want stopwords...
stopwords.indexOf(cleaned) > -1 ||
// ...nor string which would evaluate to numbers
!(isNaN(parseInt(cleaned))) ||
!(isNaN(parseFloat(cleaned)))
) {
return
}
emit(cleaned,document._id)
}
)
}
},
// Reduce function
function(k,v){
// Kind of ugly, but works.
// Improvements more than welcome!
var values = { 'documents': []};
v.forEach(
function(vs){
if(values.documents.indexOf(vs)>-1){
return
}
values.documents.push(vs)
}
)
return values
},
{
// We need this for two reasons...
finalize:
function(key,reducedValue){
// First, we ensure that each resulting document
// has the documents field in order to unify access
var finalValue = {documents:[]}
// Second, we ensure that each document is unique in said field
if(reducedValue.documents) {
// We filter the existing documents array
finalValue.documents = reducedValue.documents.filter(
function(item,pos,self){
// The default return value
var loc = -1;
for(var i=0;i<self.length;i++){
// We have to do it this way since indexOf only works with primitives
if(self[i].valueOf() === item.valueOf()){
// We have found the value of the current item...
loc = i;
//... so we are done for now
break
}
}
// If the location we found equals the position of item, they are equal
// If it isn't equal, we have a duplicate
return loc === pos;
}
);
} else {
finalValue.documents.push(reducedValue)
}
// We have sanitized our data, now we can return it
return finalValue
},
// Our result are written to a collection called "words"
out: "words"
}
)
Running this mapReduce against your example would result in db.words look like this:
{ "_id" : "can", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "canada", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "candid", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "candle", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "candy", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "cannister", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
{ "_id" : "canvas", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
Note that the individual words are the _id of the documents. The _id field is indexed automatically by MongoDB. Since indices are tried to be kept in RAM, we can do a few tricks to both speed up autocompletion and reduce the load put to the server.
Step 2: Query for autocompletion
For autocompletion, we only need the words, without the links to the documents.
Since the words are indexed, we use a covered query – a query answered only from the index, which usually resides in RAM.
To stick with your example, we would use the following query to get the candidates for autocompletion:
db.words.find({_id:/^can/},{_id:1})
which gives us the result
{ "_id" : "can" }
{ "_id" : "canada" }
{ "_id" : "candid" }
{ "_id" : "candle" }
{ "_id" : "candy" }
{ "_id" : "cannister" }
{ "_id" : "canteen" }
{ "_id" : "canvas" }
Using the .explain() method, we can verify that this query uses only the index.
{
"cursor" : "BtreeCursor _id_",
"isMultiKey" : false,
"n" : 8,
"nscannedObjects" : 0,
"nscanned" : 8,
"nscannedObjectsAllPlans" : 0,
"nscannedAllPlans" : 8,
"scanAndOrder" : false,
"indexOnly" : true,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"_id" : [
[
"can",
"cao"
],
[
/^can/,
/^can/
]
]
},
"server" : "32a63f87666f:27017",
"filterSet" : false
}
Note the indexOnly:true field.
Step 3: Query the actual document
Albeit we will have to do two queries to get the actual document, since we speed up the overall process, the user experience should be well enough.
Step 3.1: Get the document of the words collection
When the user selects a choice of the autocompletion, we have to query the complete document of words in order to find the documents where the word chosen for autocompletion originated from.
db.words.find({_id:"canteen"})
which would result in a document like this:
{ "_id" : "canteen", "value" : { "documents" : [ ObjectId("553e435f20e6afc4b8aa0efb") ] } }
Step 3.2: Get the actual document
With that document, we can now either show a page with search results or, like in this case, redirect to the actual document which you can get by:
db.yourCollection.find({_id:ObjectId("553e435f20e6afc4b8aa0efb")})
Notes
While this approach may seem complicated at first (well, the mapReduce is a bit), it is actual pretty easy conceptually. Basically, you are trading real time results (which you won't have anyway unless you spend a lot of RAM) for speed. Imho, that's a good deal. In order to make the rather costly mapReduce phase more efficient, implementing Incremental mapReduce could be an approach – improving my admittedly hacked mapReduce might well be another.
Last but not least, this way is a rather ugly hack altogether. You might want to dig into elasticsearch or lucene. Those products imho are much, much more suited for what you want.
Thanks to #Markus solution, I came up with something similar with aggregations instead. Knowing that map-reduce are flagged as deprecated for later versions.
const { MongoDBNamespace, Collection } = require('mongodb')
//.replace(/(\b(\w{1,3})\b(\W|$))/g,'').split(/\s+/).join(' ')
const routine = `function (text) {
const stopwords = ['the', 'this', 'and', 'or', 'id']
text = text.replace(new RegExp('\\b(' + stopwords.join('|') + ')\\b', 'g'), '')
text = text.replace(/[;,.]/g, ' ').trim()
return text.toLowerCase()
}`
// If the pipeline includes the $out operator, aggregate() returns an empty cursor.
const agg = [
{
$match: {
a: true,
d: false,
},
},
{
$project: {
title: 1,
desc: 1,
},
},
{
$replaceWith: {
_id: '$_id',
text: {
$concat: ['$title', ' ', '$desc'],
},
},
},
{
$addFields: {
cleaned: {
$function: {
body: routine,
args: ['$text'],
lang: 'js',
},
},
},
},
{
$replaceWith: {
_id: '$_id',
text: {
$trim: {
input: '$cleaned',
},
},
},
},
{
$project: {
words: {
$split: ['$text', ' '],
},
qt: {
$const: 1,
},
},
},
{
$unwind: {
path: '$words',
includeArrayIndex: 'id',
preserveNullAndEmptyArrays: true,
},
},
{
$group: {
_id: '$words',
docs: {
$addToSet: '$_id',
},
weight: {
$sum: '$qt',
},
},
},
{
$sort: {
weight: -1,
},
},
{
$limit: 100,
},
{
$out: {
db: 'listings_db',
coll: 'words',
},
},
]
// Closure for db instance only
/**
*
* #param { MongoDBNamespace } db
*/
module.exports = function (db) {
/** #type { Collection } */
let collection
/**
* Runs the aggregation pipeline
* #return {Promise}
*/
this.refreshKeywords = async function () {
collection = db.collection('listing')
// .toArray() to trigger the aggregation
// it returns an empty curson so it's fine
return await collection.aggregate(agg).toArray()
}
}
Please check for very minimal changes for your convenience.

Missing result in Google Places API

When using Google Maps to search for a store called "Netto" near some geographic coordinates
http://maps.google.de/maps?q=netto+near+49.046054,8.380787+&hl=de&ll=49.047475,8.379607&spn=0.01194,0.027874&sll=49.046054,8.380787&sspn=0.011941,0.027874&hq=netto&t=m&z=16
I correctly see two results.
However, doing the same search with the Google Places API
https://maps.googleapis.com/maps/api/place/search/json?location=49.046054,8.380787&radius=1000&sensor=false&keyword=netto&key=[keyremoved]
yields only a single result:
{
"html_attributions" : [],
"results" : [
{
"geometry" : {
"location" : {
"lat" : 49.0459950,
"lng" : 8.3811020
}
},
"icon" : "http://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png",
"id" : "51b5bafdd6224ba1721c9708d59aa0ed7f8377e2",
"name" : "Netto Marken-Discount AG & Co. KG",
"reference" : "CoQBdwAAALWmu5j28xIY3s9oBL9Vs8vBCmcghvKWlV9HbHysvQgZ4tMmQ_awemt0k9CA3U9KTRa-CMWq1owOZL1cSLOjKIDb8LdHce3yhIJpJ2fsUnsnFFF1b02gnJK5m5e-4E85_5gTrPZhZIDYrrFenrGyBpWLHkYSFfn0Ir3_zOJlKVWeEhC3RsEsXWqDJ1yizMOR0gvVGhREdmbn0G6GYcAsm4jGJxgJeNbLiw",
"types" : [ "establishment" ],
"vicinity" : "Dürerstr. 3b, Karlsruhe"
}
],
"status" : "OK"
}
The radius in the query is 1000m which is definitely sufficient (the distance reported by Google Maps is <400m). Changing the keyword-parameter for name doesn't help either.
https://maps.googleapis.com/maps/api/place/search/json?location=49.046054,8.380787&radius=1000&sensor=false&name=netto&key=[keyremoved]
What could I do to get both results?