{"result":{"depiction_url":null,"edges":{"observes":{"objects":[{"created":"2026-10-06T14:21:18Z","object_id":{"id":1517,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_pivot_fields","title":"observe_pivot_fields\/3","uri":"https:\/\/zotonic.com\/id\/1517"},"seq":1},{"created":"2026-10-06T14:21:18Z","object_id":{"id":1801,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_rsc_get","title":"observe_rsc_get\/3","uri":"https:\/\/zotonic.com\/id\/1801"},"seq":2},{"created":"2026-10-06T14:21:18Z","object_id":{"id":1823,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_postback_notify","title":"observe_postback_notify\/2","uri":"https:\/\/zotonic.com\/id\/1823"},"seq":3},{"created":"2026-10-06T14:21:18Z","object_id":{"id":1982,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_search_query","title":"observe_search_query\/2","uri":"https:\/\/zotonic.com\/id\/1982"},"seq":4},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2135,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_pivot_update","title":"observe_pivot_update\/3","uri":"https:\/\/zotonic.com\/id\/2135"},"seq":5},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2341,"is_a":["text","documentation","reference","notification"],"name":"doc_notification_search_query_term","title":"observe_search_query_term\/2","uri":"https:\/\/zotonic.com\/id\/2341"},"seq":6}],"predicate":{"id":2550,"is_a":["meta","predicate"],"name":"observes","title":{"_type":"trans","tr":{"en":"Observes"}},"uri":"https:\/\/zotonic.com\/id\/observes"}},"subject":{"objects":[{"created":"2026-10-06T14:21:18Z","object_id":{"id":2555,"is_a":["categorization","keyword","keyword_information_type"],"name":"zotonic_topic_reference","title":"Reference","uri":"https:\/\/zotonic.com\/id\/2555"},"seq":1},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2562,"is_a":["categorization","keyword","keyword_audience"],"name":"zotonic_topic_site_administrator","title":"Site administrator","uri":"https:\/\/zotonic.com\/id\/2562"},"seq":2},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2586,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_search_and_discovery","title":"Search and discovery","uri":"https:\/\/zotonic.com\/id\/2586"},"seq":3},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2599,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_api_and_integration","title":"API and integration","uri":"https:\/\/zotonic.com\/id\/2599"},"seq":4},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2603,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_configuration","title":"Configuration","uri":"https:\/\/zotonic.com\/id\/2603"},"seq":5},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2609,"is_a":["categorization","keyword","keyword_domain"],"name":"zotonic_topic_geolocation","title":"Geolocation","uri":"https:\/\/zotonic.com\/id\/2609"},"seq":6},{"created":"2026-10-06T14:21:18Z","object_id":{"id":2635,"is_a":["categorization","keyword","keyword_architecture"],"name":"zotonic_topic_module","title":"Module","uri":"https:\/\/zotonic.com\/id\/2635"},"seq":7}],"predicate":{"id":308,"is_a":["meta","predicate"],"name":"subject","title":{"_type":"trans","tr":{"en":"Keyword"}},"uri":"http:\/\/purl.org\/dc\/elements\/1.1\/subject"}}},"id":2769,"is_a":["text","documentation","reference","module"],"links":[{"rel":"self","target":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/2769"},{"rel":"hub","target":"https:\/\/zotonic.com\/.zotonic\/websub"}],"medium":null,"medium_url":null,"name":"doc_external_4_mod_geomap","page_url":{"en":"https:\/\/zotonic.com\/docs\/2769\/mod_geomap","x-default":"https:\/\/zotonic.com\/docs\/2769\/mod_geomap"},"preview_url":null,"resource":{"version":29,"pivot_location_lat":null,"title":"mod_geomap","is_authoritative":true,"body":"<p>Add resource locations, map display, geocoding, and geographic searches to\nZotonic. Enable <code>mod_geomap<\/code> on the site; it depends on <code>mod_l10n<\/code>.<\/p>\n<h2>Resource locations<\/h2>\n<p>The admin location editor stores <code>location_lat<\/code>, <code>location_lng<\/code>, and\n<code>location_zoom_level<\/code>. Coordinates are latitude and longitude in degrees.\nResource pivoting updates <code>pivot_location_lat<\/code>, <code>pivot_location_lng<\/code>, and the\nquadtile <code>pivot_geocode<\/code>. Explicit coordinates take precedence over a location\nderived from address fields. An already supplied pivot location is retained\nwhen no explicit coordinates are present.<\/p>\n<p>Resource reads expose <code>computed_location_lat<\/code> and <code>computed_location_lng<\/code> from\nthe quadtile, or from numeric explicit coordinates when no quadtile is available.\nUse these computed properties for map display and the <code>geomap_distance<\/code> filter.<\/p>\n<p>Address geocoding uses the resource&#39;s street, city, state, postcode, and country.\nFull address lookups try Google Maps and fall back to OpenStreetMap Nominatim;\ncountry lookups can use the module&#39;s precoded country coordinates. Address\nlookup can therefore send address information to an external service.<\/p>\n<h2>Configuration<\/h2>\n<p>Set these per-site keys under <code>mod_geomap<\/code>:<\/p>\n<table class=\"table\"><thead><tr><th>Key<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td><code>is_auto_geocode<\/code><\/td><td>Automatically use external geocoding for resource addresses; defaults to <code>true<\/code>.<\/td><\/tr><tr><td><code>provider<\/code><\/td><td><code>googlemaps<\/code> selects Google Maps; otherwise the bundled map templates use OpenLayers.<\/td><\/tr><tr><td><code>google_api_key<\/code><\/td><td>Key used for Google geocoding and the browser Maps script.<\/td><\/tr><tr><td><code>location_lat<\/code><\/td><td>Initial latitude when the resource has no location; the admin template falls back to <code>0<\/code>.<\/td><\/tr><tr><td><code>location_lng<\/code><\/td><td>Initial longitude when the resource has no location; the admin template falls back to <code>0<\/code>.<\/td><\/tr><tr><td><code>zoomlevel<\/code><\/td><td>Initial zoom when no resource-specific zoom is available.<\/td><\/tr><\/tbody><\/table>\n<p>The OpenLayers admin map defaults to zoom <code>2<\/code> without a location and to <code>15<\/code>\nfor a located resource without its own zoom setting. The static-map tag has its\nown default zoom of <code>14<\/code>. The provider setting selects map presentation; it does\nnot change the geocoding fallback order. The Google key is exposed to map\nscripts through <code>m.geomap.google_api_key<\/code> and is not a server-only secret.<\/p>\n<p>Set <code>mod_geomap.is_auto_geocode<\/code> to <code>false<\/code> to prevent resource pivoting from\nsending addresses to Google Maps or Nominatim. Existing coordinates are retained\nwhen a lookup is skipped. Explicit coordinates and local precoded lookups still\nwork. The admin&#39;s <strong>Set to entered address<\/strong> button and explicit Erlang geocoding\ncalls remain available and can contact external services.<\/p>\n<h2>Nearby search<\/h2>\n<p>The <code>geo_nearby<\/code> search accepts a resource <code>id<\/code> with pivoted coordinates or an\nexplicit <code>latitude<\/code> and <code>longitude<\/code> pair. <code>distance<\/code> is in kilometers and defaults\nto <code>10<\/code>. Use <code>cat<\/code> to restrict resource categories.<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% with m.search.geo_nearby::%{ id: id, distance: 10 } as results %}\n    {% for location_id in results %}\n        &lt;a href=&quot;{{ location_id.page_url }}&quot;&gt;{{ location_id.title }}&lt;\/a&gt;\n    {% endfor %}\n{% endwith %}\n<\/code><\/pre>\n<p>The search selects a latitude\/longitude bounding box and orders results by\nsquared coordinate differences from the center. It is an approximate nearby\nsearch, not an exact circular-distance filter. The center resource is not\nexcluded. Missing or unusable center coordinates do not produce a valid query.\nThe normal Zotonic search pipeline applies resource visibility restrictions.<\/p>\n<p>The <code>has_geo<\/code> search term selects resources with both pivot coordinates present;\n<code>has_geo=false<\/code> selects resources missing either coordinate.<\/p>\n<h2>Admin panels<\/h2>\n<p>Enabling the module adds a <strong>Geolocation<\/strong> panel to resource editing. It starts\ncollapsed and loads the map lazily. The category&#39;s <strong>Show geo data on edit page<\/strong>\nfeature controls its visibility; if unset, it follows <strong>Show address<\/strong>, defaulting\nto enabled. Resources with explicit latitude or longitude also show the panel.<\/p>\n<p>Expand the panel to edit latitude, longitude, and zoom level (<code>0<\/code> through <code>29<\/code>).\nClick the map to select a location, or use these controls:<\/p>\n<ul><li><strong>Set to current location<\/strong> requests the browser&#39;s location, subject to the\nvisitor&#39;s permission and browser availability.<\/li><li><strong>Set to entered address<\/strong> geocodes the address in the edit form. This button\nis hidden when the form has no address-country field.<\/li><li><strong>Clear<\/strong> removes the explicit coordinates from the form.<\/li><li><strong>Reset<\/strong> restores the resource&#39;s saved coordinates.<\/li><\/ul>\n<p>Save the resource to persist the edited values. The indexed coordinates shown\nbelow the inputs are the computed location; pivoting updates the searchable\nlocation after a save. Clearing explicit coordinates can allow address-derived\ngeocoding to determine the location again.<\/p>\n<p>Country resources also receive a <strong>World Map<\/strong> panel with <strong>Color<\/strong> and <strong>Value<\/strong>\nfields. These store <code>map_color<\/code> and <code>map_value<\/code> for the country data returned by\n<code>m.geomap.countries<\/code>; they do not set the resource&#39;s coordinates.<\/p>\n<h2>Showing a map<\/h2>\n<h3>Static tile map<\/h3>\n<p>Render a resource&#39;s computed location without interactive-map JavaScript:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% geomap_static id=id zoom=14 n=2 %}\n<\/code><\/pre>\n<p>Or pass explicit coordinates:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% geomap_static latitude=52.34322 longitude=4.33423 zoom=14 %}\n<\/code><\/pre>\n<p>The tag renders <code>_geomap_static.tpl<\/code>, a grid of OpenStreetMap tile images with a\nmarker. <code>n<\/code> defaults to two rows and columns; <code>rows<\/code> and <code>cols<\/code> can override each\ndimension, and <code>size<\/code> sets the displayed tile size (default <code>256<\/code> pixels).\nOverride this template in the site to change its markup or styling.<\/p>\n<p>For a single remotely rendered map image, include:<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% include &quot;_geomap_static_simple.tpl&quot; id=id zoom=14 width=440 height=280 %}\n<\/code><\/pre>\n<p>This template uses the resource&#39;s computed location and the external\n<code>staticmap.openstreetmap.de<\/code> image service. Its default image dimensions are\n<code>220<\/code> by <code>220<\/code> pixels. Both static approaches require a usable location.<\/p>\n<h3>Interactive OpenLayers map<\/h3>\n<p>The <code>do_geomap<\/code> widget supports panning, zooming, and markers. With the map\nprovider unset or set to <code>openlayers<\/code>, load <code>_js_geomap.tpl<\/code> after the site&#39;s\nstandard jQuery\/Zotonic scripts and before widget initialization. Keep the\nstandard <code>{% all include &quot;_html_head.tpl&quot; %}<\/code> hook in the page head to load the\nmodule&#39;s OpenLayers CSS. Include the map scripts once per page.<\/p>\n<pre class=\"notranslate\"><code class=\"notranslate language-django\">{% include &quot;_js_geomap.tpl&quot; %}\n&lt;div class=&quot;do_geomap&quot; style=&quot;width: 100%; height: 400px;&quot;\n     data-geomap=&#39;{&quot;location_lat&quot;:52.34322,&quot;location_lng&quot;:4.33423,&quot;zoom&quot;:14,&quot;marker&quot;:true}&#39;&gt;\n&lt;\/div&gt;\n<\/code><\/pre>\n<p>The map container needs an explicit height. <code>location_lat<\/code> and <code>location_lng<\/code>\nset its center, <code>zoom<\/code> sets the initial zoom, and <code>marker=true<\/code> adds a marker\nthere. For resource coordinates, read <code>id.computed_location_lat<\/code> and\n<code>id.computed_location_lng<\/code>, check that they are numeric, and serialize the\noptions as JSON with HTML-attribute escaping rather than concatenating strings.<\/p>\n<p>For multiple locations, supply a <code>locations<\/code> array of objects containing <code>id<\/code>,\n<code>lat<\/code>, and <code>lng<\/code>, with optional <code>icon<\/code> and <code>data<\/code>. The OpenLayers widget clusters\nnearby markers; <code>clusterDistance<\/code>, <code>clusterBgColor<\/code>, and <code>clusterColor<\/code> control\ncluster presentation. Marker clicks invoke the <code>map_infobox<\/code> wired event with\nIDs and associated data, which the site can handle to display details.<\/p>\n<p>The older <code>_geomap.tpl<\/code> is experimental and uses an older OpenLayers API; use\nthe widget with the bundled current assets for new templates.<\/p>\n<h3>Related components<\/h3>\n<p><code>m.geomap.countries<\/code> reads the bundled\n<code>priv\/data\/internet_users_2005_choropleth_lowres.json<\/code> from the\n<code>zotonic_mod_geomap<\/code> application using <code>code:priv_dir\/1<\/code>. No copy in the site\ndirectory is needed. Decoded geometry is cached per site for one day, or until\nthe site&#39;s depcache is flushed. Resource values and visibility checks are\napplied on every request. Flush the cache after replacing the data file to\nload the new geometry immediately.<\/p>\n<p>Use <code>geomap_distance<\/code> for distances between resource locations or coordinate\nmaps. <code>m.geomap<\/code> exposes map configuration and country-map data. Its <code>nearby<\/code> and\n<code>locations<\/code> paths currently return empty maps; use <code>geo_nearby<\/code> for resource\nsearches instead of relying on the legacy service descriptions in the README.<\/p>\n<p>The module observes resource reads, pivot updates\/fields, search queries and\nterms, and map popup postbacks. Popup resource lists are filtered for visibility.<\/p>","slug":"mod_geomap","doc_module_config":[{"default":"true","description":"Automatically look up resource addresses using external geocoding services. Disable to keep automatic lookups local; manual lookups remain available.","has_default":true,"key":"is_auto_geocode","module":"mod_geomap","type":"boolean"},{"default":"&quot;openlayers&quot;","description":"Map presentation provider: openlayers or googlemaps. Does not change the geocoding services used for address lookups.","has_default":true,"key":"provider","module":"mod_geomap","type":"string"},{"default":"undefined","description":"Google Maps API key for server-side geocoding and browser maps. Exposed to browser scripts through m.geomap.google_api_key.","has_default":true,"key":"google_api_key","module":"mod_geomap","type":"string"},{"default":"0","description":"Initial map latitude in degrees when the resource has no location. The admin map falls back to 0; does not set resource coordinates.","has_default":true,"key":"location_lat","module":"mod_geomap","type":"float"},{"default":"0","description":"Initial map longitude in degrees when the resource has no location. The admin map falls back to 0; does not set resource coordinates.","has_default":true,"key":"location_lng","module":"mod_geomap","type":"float"},{"default":"2","description":"Initial admin map zoom (0..29) when the resource has no location or zoom setting. Located resources default to 15; static maps have a separate default of 14.","has_default":true,"key":"zoomlevel","module":"mod_geomap","type":"integer"}],"is_protected":false,"visible_for":0,"tz":"UTC","language":["en"],"doc_source_hash":"4c1f7bc4b460a8538bd48bd335be8e4523b6d715088d2038e15e5e6e2d465fc7","doc_module_observers":[{"name":"pivot_fields","page_name":"doc_notification_pivot_fields"},{"name":"pivot_update","page_name":"doc_notification_pivot_update"},{"name":"postback_notify","page_name":"doc_notification_postback_notify"},{"name":"rsc_get","page_name":"doc_notification_rsc_get"},{"name":"search_query","page_name":"doc_notification_search_query"},{"name":"search_query_term","page_name":"doc_notification_search_query_term"}],"is_featured":false,"content_group_id":{"id":2551,"is_a":["meta","content_group"],"name":"content_group_imported_docs","title":"Imported documentation","uri":"https:\/\/zotonic.com\/id\/content_group_imported_docs"},"external_module_id":{"id":4,"is_a":[],"name":null,"title":null,"uri":"https:\/\/zotonic.com\/id\/4"},"git_url":"https:\/\/github.com\/zotonic\/zotonic_mod_geomap","category_id":{"id":320,"is_a":["meta","category"],"name":"module","title":"Modules","uri":"https:\/\/test.zotonic.com\/id\/320"},"doc_source_path":".\/src\/mod_geomap.erl","publication_start":"2026-10-06T14:21:18Z","github_url":"https:\/\/github.com\/zotonic\/zotonic_mod_geomap","pivot_location_lng":null,"doc_source_kind":"external","name":"doc_external_4_mod_geomap","is_unfindable":false,"is_published":true,"pivot_geocode":null,"external_module_title":"GeoMap","created":"2026-10-06T14:21:18Z","uri":null,"doc_status":"current","is_dependent":false,"is_external_module":true,"publication_end":"9999-06-01T00:00:00Z","modifier_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"privacy":0,"doc_source_commit":"e6d27c20d16c273462eb8b6b0dfac4556a9ba391","creator_id":{"id":1,"is_a":["person"],"name":"administrator","title":"Site Administrator","uri":"https:\/\/zotonic.com\/id\/1"},"modified":"2026-10-06T14:35:28Z","title_slug":"mod_geomap"},"uri":"https:\/\/zotonic.com\/id\/2769","uri_template":"https:\/\/zotonic.com\/id\/:id","websub":{"hub":"https:\/\/zotonic.com\/.zotonic\/websub","topic":"https:\/\/zotonic.com\/.zotonic\/websub\/topic\/2769"}},"status":"ok"}