Posted by derjochenmeyer on January 26, 2011 at 10:17am
34 followers
| Project: | Geolocation Field |
| Version: | 7.x-1.x-dev |
| Component: | Geolocation field |
| Category: | task |
| Priority: | normal |
| Assigned: | Unassigned |
| Status: | active |
Issue Summary
We need to discuss IF and HOW Geolocation Field should store address information (either geocoded or entered by the user).
Google's geocoder for example returns *a lot* information (Try this demo). Here is an example, somewhere on La Rambla in Barcelona, Spain.
- Lat: 41.38131735079649
- Lng: 2.173009026050594
- street_number: 77
- route: La Rambla
- locality, political: Barcelona
- administrative_area_level_2, political: Province Barcelona
- administrative_area_level_1, political: Catalonia
- country, political: Spain
- postal_code: 08002
Google's geocoder is just one widget that helps to enter/generate this information. Geolocation Filed should be flexible enough to provide ONE database structure for any kind of input widget.
So first we need to define which information we want to store.
Comments
#1
I needed to display the address along with the map. All of the logic to deal with the addresses and everything was already there. I added it to the schema, then added options to the google maps widget. The address in this case is simply the "address" text field that is already in use. Here's the patch.
I agree with storing the full geocode data. I simply did this as a quick solution. Hopefully you can gain something from it.
#2
Another way of doing based on a classical text field:
- add a text field in the content type.
- delete the text field in the module Goole map widget
- modify the js file to:
- impact the added text field for input => replace all off $(.geolocation-input) by $("#edit-field-geolocation").prev().find(':text').
- always update the formatted address from google in the text field => delete condition on mapclick line 21
I am new to Drupal so I even if it is working I don't know if it is a good way of doing... (and sorry I will learn to do patch files)
note: if you want to have more than one field using the module, you can too replace $(.geolocation-input) by $("[id^='edit-field-geo']") so that any field name field_geo... is impacted by the script.
#3
Thanks for sharing this, supportPIC.
There are several possible routes that we could take in developing Geolocation Field.
#241484: Geolocation proximity filter for views.
All other data could be made available in tokens, and every user could choose WHAT he/she wants to store and WHERE (e.g. text fields). That's what supportPIC is suggesting and what we started to discuss here:
#1036056: Suggestion: Populate custom fields with geocoded data
That sounds the best option for me. We could start with the hCard (Wikipedia) microformat as a foundation. Here is another hCard specification. This sounds promising:
I want a solid, easy solution for the data storage and output. All fancy stuff can be accomplished by custom widgets and formaters.
Coming from there. If we want to be able to output this:
<div class="vcard"><div class="fn org">Some Company in Barcelona</div>
<div class="adr">
<div class="street-address">77 La Rambla</div>
<div> <span class="locality">Barcelona</span>, <span class="region">Catalonia</span> <span class="postal-code">08002</span></div>
<div class="country-name">Spain</div>
</div>
<div class="geo">
<div class="latitude">41.38131735079649</div>
<div class="longitude">2.173009026050594</div>
</div>
</div>
We need to store this (Barcelona Example from above):
Please feel free to tell me I'm wrong.
#4
Subscribing
#5
@derjochenmeyer: Is there anything that precludes implementing option 2. first and then adding elements of option 1. as an additional feature?
For instance, in addition to storing the address as an hCard microformat, tokens could be provided for those who want to store the address some other way. I like a native and simple way to store the address that is provided by the module, but the flexibility to store it differently if needed.
We could then provide a checkbox setting that enables storage of the address by the module itself (as an hCard microformat). If this setting was left unchecked, then it would be up to the site administrator to use the tokens to store the data some other way. To facilitate this, we could provide a Rules module (http://drupal.org/project/rules) event when "An address was geocoded". Then using Rules' default "Set a data value" action (in combination with tokens provided by the module), site administrators could populate any custom fields on the site with the address information.
Thoughts?
--Ben
#6
Hi Ben, thanks for sharing your thoughts.
This sounds like a good idea.
My focus is not on storing the information "as an hCard", but rather be able to output it as such. Token and Rules integration sounds very good. Have you experience as a developer with Rules and Token?
#7
@derjochenmeyer: Sounds good. Yes, sorry for my sloppy language about storing "as an hCard"... I was just writing my comment quickly. I totally understand what you mean by output rather than storage.
As for Token and Rules integration, I've done a lot of testing/reviewing of contrib modules that integrate with those modules (as well as some limited development). However, I work regularly with a couple of developers who do a lot of development with Token and Rules. So if we were going down that path and needed some input, I could definitely ask them for some help.
Cheers,
Ben
#8
Ok good, I'll typ to come up with a patch for a first implementation. But you are very welcome to jump in. As stated on the project page "Seeking co-maintainer(s)" :)
#9
Wouldn't adding additional features like this take away from the share beauty of this module!! KISS
There are so many other geo lookup modules, like location, that are (IMHO) buggy and bloated.
An alternative approach is an independent module that supports multiple lookup engines & stores it's data in a custom multi-component field, which one of these could be a geolocation field.
#10
@Alan: Isn't this thread simply talking about simply storage and display which are what a "field" does?
Storing the data in a detailed and structured manner drastically widens the possibilities of what can be done.
#11
fair enough.
here is a quick old school way (hacked so not to interact with existing module), like imagefield data array.
some wtf occurred, like serialize wasn't picked up & when using multiples, hook_field_is_empty() was not being called. possibly both major field bugs if there is not errors in my coding.
bad draw back - serialized data so bad / hard views integration
<?php
// $Id$
/**
* @file
* A latlng field using the Field API.
*/
/**
* Implements hook_field_info().
*/
function latlng_field_info() {
return array(
'latlng' => array(
'label' => t('Lat Lng'),
'description' => t('Lat Lng input.'),
'default_widget' => 'latlng_text',
'default_formatter' => 'latlng_text',
),
);
}
/**
* Implements hook_field_validate().
*/
function latlng_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
foreach ($items as $delta => $item) {
if (!empty($item['lat'])) {
if ( !is_numeric($item['lat']) ) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'latlng_invalid',
'message' => t('Invalid Latitude.'),
);
}
if ( !is_numeric($item['lng']) ) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'latlng_invalid',
'message' => t('Invalid Longitude.'),
);
}
}
}
}
/**
* Implements hook_field_presave().
* $entity_type, $entity, $field, $instance, $langcode, &$items
*/
function latlng_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
unset($items['add_more']);
foreach ($items AS $delta => $item) {
if (latlng_field_is_empty($item, $field)) {
unset($items[$delta]);
}
else {
$item['lat'] = $item['coords']['lat'];
$item['lng'] = $item['coords']['lng'];
$item['data'] = serialize($item['data']);
$item = array_intersect_key($item, array('lat' => '', 'lng' => '', 'data' => ''));
$items[$delta] = $item;
}
}
}
/**
* Implements hook_field_is_empty().
*/
function latlng_field_is_empty($item, $field) {
if ( empty($item['coords']['lat']) && $item['coords']['lat'] !== '0' ) {
return TRUE;
}
if ( empty($item['coords']['lng']) && $item['coords']['lng'] !== '0' ) {
return TRUE;
}
}
/**
* Implements hook_field_formatter_info().
*/
function latlng_field_formatter_info() {
return array(
'latlng_text' => array(
'label' => t('Simple text-based formatter'),
'field types' => array('latlng'),
),
'latlng_latitude' => array(
'label' => t('Latitude text-based formatter'),
'field types' => array('latlng'),
),
'latlng_longitude' => array(
'label' => t('Longitude text-based formatter'),
'field types' => array('latlng'),
),
);
}
/**
* Implements hook_field_formatter_view().
*/
function latlng_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
switch ($display['type']) {
case 'latlng_text':
foreach ($items as $delta => $item) {
$element[$delta]['#markup'] = '<p>' . t('Geolocation is @lat, @lng', array('@lat' => $item['lat'], '@lng' => $item['lng'])) . '</p>';
}
break;
case 'latlng_latitude':
foreach ($items as $delta => $item) {
$element[$delta]['#markup'] = $item['lat'];
}
break;
case 'latlng_longitude':
foreach ($items as $delta => $item) {
$element[$delta]['#markup'] = $item['lng'];
}
break;
}
return $element;
}
/**
* Implements hook_field_widget_info().
*/
function latlng_field_widget_info() {
return array(
'latlng_text' => array(
'label' => t('Latitude/Longitude'),
'field types' => array('latlng'),
),
);
}
function latlng_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
foreach ($entities as $id => $entity) {
foreach ($items[$id] as $delta => $item) {
$items[$id][$delta]['data'] = !empty($item['data']) ? unserialize($item['data']) : array();
}
}
}
/**
* Implements hook_field_widget_form().
*/
function latlng_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$lat_value = isset($items[$delta]['lat']) ? $items[$delta]['lat'] : '';
$lng_value = isset($items[$delta]['lng']) ? $items[$delta]['lng'] : '';
$data = empty($items[$delta]['data']) ? array() : $items[$delta]['data'];
$element += array(
'#delta' => $delta,
);
$field['settings'] += array(
'components' => array(),
'labels' => array(),
);
$field['settings']['components'] = array_filter($field['settings']['components']);
switch ($instance['widget']['type']) {
case 'latlng_text':
$element['data'] = array(
'#tree' => TRUE,
);
foreach(latlng_field_components() as $key => $components) {
if (in_array($key, $field['settings']['components'])) {
$element['data'][$key] = array(
'#type' => $components['type'],
'#title' => empty($field['settings']['labels'][$key])
? $components['title'] : $field['settings']['labels'][$key],
'#default_value' => isset($data[$key]) ? $data[$key] : '',
);
}
}
$element['coords']['#prefix'] = '<div class="container-inline">';
$element['coords']['#suffix'] = '</div>';
$element['coords']['lat'] = array(
'#title' => t('Latitude'),
'#type' => 'textfield',
'#default_value' => $lat_value,
'#size' => 15,
'#maxlength' => 30,
);
$element['coords']['lng'] = array(
'#title' => t('Longitude'),
'#type' => 'textfield',
'#default_value' => $lng_value,
'#size' => 15,
'#maxlength' => 30,
);
break;
}
return $element;
}
/**
* Implements hook_field_widget_error().
*/
function latlng_field_widget_error($element, $error, $form, &$form_state) {
switch ($error['error']) {
case 'latlng_invalid':
form_error($element, $error['message']);
break;
}
}
/**
* Implements hook_field_schema().
*/
function latlng_field_schema($field) {
$columns = array(
'lat' => array(
'description' => 'Stores the latitude value',
'type' => 'float',
'size' => 'big',
'not null' => TRUE,
'default' => 0,
),
'lng' => array(
'description' => 'Stores the longitude value',
'type' => 'float',
'size' => 'big',
'not null' => TRUE,
'default' => 0,
),
'data' => array(
'type' => 'text',
'description' => 'Stores additional data',
),
);
$indexes = array(
'lat' => array('lat'),
'lng' => array('lng'),
);
return array(
'columns' => $columns,
'indexes' => $indexes,
);
}
function latlng_field_components() {
$components = array(
'name' => array('title' => t('Name'), 'type' => 'textfield'),
'desc' => array('title' => t('Description'), 'type' => 'textarea'),
'addr1' => array('title' => t('Addr 1'), 'type' => 'textfield'),
'addr2' => array('title' => t('Addr 2'), 'type' => 'textfield'),
'addr3' => array('title' => t('Addr 3'), 'type' => 'textfield'),
'addr4' => array('title' => t('Addr 4'), 'type' => 'textfield'),
'addr5' => array('title' => t('Addr 5'), 'type' => 'textfield'),
'addr6' => array('title' => t('Addr 6'), 'type' => 'textfield'),
'region' => array('title' => t('Region'), 'type' => 'textfield'),
'country' => array('title' => t('Country'), 'type' => 'textfield'),
'postcode' => array('title' => t('Post code'), 'type' => 'textfield'),
);
return $components;
}
/**
* Implements hook_field_settings_form().
*/
function latlng_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$settings += array(
'components' => array(),
'labels' => array(),
);
$form = array('#tree' => TRUE);
$components = latlng_field_components();
$options = array();
foreach ($components as $key => $info) {
$options[$key] = $info['title'] .'('. $info['type'] .')';
}
$form['components'] = array(
'#type' => 'checkboxes',
'#title' => t('Components'),
'#default_value' => $settings['components'],
'#description' => t('Store additional information about the location.'),
'#options' => $options,
);
$form['labels'] = array();
foreach ($components as $key => $info) {
$form['labels'][$key] = array(
'#type' => 'textfield',
'#title' => t('Label for !title', array('!title' => $info['title'])),
'#default_value' => isset($settings['labels'][$key]) ? $settings['labels'][$key] : '',
);
}
return $form;
}
?>
#12
Would not defining an API be the best route - define a hook_geolocation_field_store_address_info... and pass all address information from the google api in the hook? Then any contrib module could implement and manage the data. It would keep the geolocation field module separate and not truly responsible for the address data management.
#13
Much of the function requests in this thread can be found using the OpenLayers Geocoder and OpenLayers Proximity modules. However, OpenLayers will not support any of these functions in Drupal 7.
Here I broke down the basic strategy of using a third party service (Google Maps API) to populate via Ajax text fields in a node form based on autocomplete by example of OpenLayers Geocoder. http://drupal.org/node/1044286#comment-4062056
If the Geolocation module can map Lat and Lon to a text field, the text fields then can be used to build an OpenLayers data map using Views. Super easy. To sum up extending this module is make it use information that is already available to populate text fields for other modules to use.
I would very much like to see an address widget that function exactly like the one on the add a business form of yelp.com.
#14
#13 +1
#15
I just briefly took a look at this thread and I'm way too tired right now to take a serious look at anything, but I do have a couple of thoughts. First a little background, I've been working on a project in Drupal 6 using Openlayers Geocoder and Openlayers Proximity. There doesn't seem to be any interest on the part of the maintainers in porting those projects to Drupal 7, which is why I'm here. Now for my thoughts.
I probably have way too much to say for my level of expertise. I'm still trying to figure out how to do things in Drupal 6, but I think that may be about to change.
#16
There is a discussion going on at http://groups.drupal.org/location-and-mapping. You should get involved.
#17
Why not integrate it with a module that already stores addresses?
http://drupal.org/project/addressfield
#18
@lloydpearsoniv that is one of the things we are discussing, if you follow the URL in #16. The group have created a wiki page where we try to sum it up, and a discussion thread Building the ultimate street address management system and geocoding module for Drupal 7. Pls join us over there :)
#19
I'm just discovering this module, and I think I'll probably end up using it. :-)
Personally I think data could be stored minimally for v.1.0. That is, have table columns for lat, long, and the precalc'ed fields. Also have a column for 'data' that will be a serialized keyed array of whatever else comes back from Google. As the project develops, various use cases will show which information needs to graduate out of the 'data' pile and into its own column. This way, there is an achievable goal for v.1.0 and clear room for change.
My first suggestion: A date/time field so you can locate yourself in spacetime.
#20
@steinmb
I followed the link & joined the convo right after i left my comment. Thanks for inviting me. ;)
#21
Hey guys, sorry to have jumped in without giving the issue a thorough read first. I've seen some good ideas here and I have to admit I'm still a little fuzzy about how things work in Drupal 7. It seems to me that the basic idea of a field type module is to just to provide the field type definitions. It looks like you need to provide a default widget and a formatter but basically the field type definition is de-coupled from both the widget definition and the field storage definition.
Am I correct in assuming that you're trying to figure out the schema for your field, and just use sql field storage? If that is the case then it would be possible to define different field types with different schemas, and people could choose the one that works best for their use case. That might actually be the way to achieve the flexibility you're looking for, rather than trying to provide a single structure that works for every kind of input widget.
If you're writing a field storage module then that's a different problem. I should probably take a look at the code. I like the idea of providing tokens that can be used to complete other fields, but as I learn more about this, I'm beginning to think that is more the job of the geo-coder module.
#22
I've tried to make a hybrid between this and the Address field module. It can be found on github at https://github.com/adam-s/addressfield/tree/Plain . You can type in the address parts then click the geocode button OR just move the marker on the map.
Since on the website I'm building everybody will know their address there isn't much need to have the reverse geocode. I just though maybe somebody else would like it so I kept it there.
#23
I heartily agree with Mile23's suggestion.
Furthermore, I suggest adding a hook invokation after the data is prepared by Geolocation Field but before serialization and insertion into the DB. It would will allow other maintainers to manage the data in a customized fashion. I envision the scenario where the data should be saved twice, once with the serialized data before the invokation, and once with the data returned by all hook invokations. It could prevent legacy issues for future versions of the Geolocation Field.
#24
@derjochenmeyer
I finally got around to checking out your module and I have to say this is nice work and I apologize being such an idiot. Focusing a little on your original thought:
AddressField is already doing that, at least the storage part, so I think it would be a mistake to duplicate what they're doing. (I've been doing a little code review and finding out that module duplication is being highly frowned upon by the powers that be.) Integration either by tokens or through an API would be way cool though.
#25
Sub
#26
subs
#27
I'm here just to say that I think using Address Field should be the way to go. It will be highly used because of commerce and seems good enough.
@derjochenmeyer
Any thoughts on going that way?
#28
Well, I don't know the current state of Addrees Field, but I think it just stores addresses. Most of the developers (me included) prefer Geofield because has integration with Openlayers ant its Views integration.
There are too many modules made to store locations in too many different ways, we should try to centralize efforts in just one and try to make it work in the most number of situations as possible.
Geolocation Field has a great input widget, so we should try to improve what it does better than any other module and let other modules to handle storage. Adding compability to geofield (#1129512: Make Google maps geolocation widget compatible with Geofield) is a necessary step in that direction.
#29
@jherencia pls read #18
#30
Subscribing, greetings, Martijn
#31
I implemented the patch in #1, but I get this error: Notice: Undefined property: stdClass::$field_location_address in field_sql_storage_field_storage_load() (line 336 of /modules/field/modules/field_sql_storage/field_sql_storage.module). Ideas?
#32
Sub
#33
subscribe
#34
@brandy brown: same here.
sub
#35
Hi guys, just an FYI that geocoder module (http://drupal.org/project/geocoder) is now stable and has an AJAX API. It might be a good idea to use it as your geocoding backend. For addresses it currently supports google, yahoo, and yandex (russian) with more on the way.
Also note that it already has support for geolocation in a non-interactive fashion. (grab an address or text field and geocode it to a geolocation field on entity save)
#36
Are there any improvements on this issue?
I really like to store address data (preferably city and country in separate places), but couldnt find a solution yet.
#37
etcetera9,
You can now do this with addressfield, geocoder, and geofield modules.
#38
@phayes,
Without using Geolocation module?
I am not sure if Geocoder does reverse geocoding yet. What I want is that user selects a point from a map, then I store the address info (city, country etc) in another field.
If you know the way to do it, can you please briefly explain it?
#39
Thanks for the input and all the thoughts on this topic.
If anybody is interested in further discussing the details, I will split this in to seperate issues for each feature. Starting with
#1780092: Introduce new 'data'-column to store whatever comes back from Google's Geocoder
Thanks Mile23 for comment #19.
#40
I would like to do the same thing like this: Use another Geolocation field for "Set location" button
But a bit different:
My idea, i make my own CCK adressfields (in the top of the screenshot below) and the user fill in the adressdata in this fields. The adressfield from the geolocation-module is hidden. The data from the CCK fields will automatically copy to the hidden geolocation-adressfield. After filling, the user click on the visible button "Get location". After that, i have all data that i need. The longitude and latitude compatible to the adressdata from the CCK-fields. But i have no acces to the geolocation-adressfield... Where is the data stored from this field? I know... not in the database... But when i edit a user i can see the data in the field, thus, the data are stored somewhere or I'm wrong? Can anybody help me with this?
#41
@FeanorCC its not stored. Only lat,lng is stored. The adress data you see, is the closest address returned by googles geocoder (reverse geocoded from lat,lng).
#42
The patch included at the top has the right idea, but is not compatible with the recent version of Geolocation. Anyone willing to rewrite it? The ability to show the address instead of "Geolocation Long 999999999 Lat 99999" is a pretty common need and should be a default option to show the Address and the Address + Map. Integration with Location Module could help.
#43
I managed to get this working. In my case, there turned out to be a select number of locations that were going to be used often in the website so I did this.
Create a Location Entity (Content Type)
- Postal Address Field for standard address information
- Geolocation Field for map
Create an Entity Reference field in the chosen entity, for my case it was a Commerce Product Type
Then your done. Obviously only works with with a specific set of locations, though the benefit of this is its able to be searchable and both the address and the map show.