## Name HTML::OSM - Generate an interactive OpenStreetMap with Leaflet.js ## Version Version 0.11 ## Synopsis ```perl use HTML::OSM; my $map = HTML::OSM->new( coordinates => [ [37.7749, -122.4194, 'San Francisco'], [undef, undef, 'Paris'], ], zoom => 10, ); my ($head, $body) = $map->onload_render(); ``` - Caching Geocode results are cached via [CHI](https://metacpan.org/pod/CHI) (default: in-memory, 1-day TTL). Supply your own `cache` object to persist across processes. - Rate-Limiting Set `min_interval` (seconds) to throttle outbound Nominatim calls and comply with the API fair-use policy. - Tile server `tile_url` replaces the OpenStreetMap tile layer, so a map can use an internal tile server or run on an air-gapped network. It accepts the same Leaflet placeholders as the default, for example `http://tiles.internal/{z}/{x}/{y}.png`. The attribution remains the OpenStreetMap credit, which is the usual requirement for OSM-derived tiles. The optional in-page search box still calls Nominatim when a reader uses it. - Tile attribution `tile_attribution` replaces the credit shown in the corner of the map. Set it when the tiles come from a provider that asks for a different credit. The value is HTML that Leaflet renders as it is, so pass only trusted text. - Nominatim contact address `email` is added to every Nominatim request the module makes, as the Nominatim usage policy asks of heavy users. It is not put into the rendered page, so the in-page search box sends no address. ## Description `HTML::OSM` builds an interactive map from OpenStreetMap tiles and the Leaflet.js library. Give it points as latitude and longitude, as place names, or as geocoder result objects. It can also draw GeoJSON shapes, heatmaps, GPX tracks and choropleth (colour-filled region) layers, and can group nearby markers into clusters. Place names are turned into coordinates by an injected geocoder or, without one, by the Nominatim service. The answers are cached, and requests can be spaced out with `min_interval`. `onload_render()` returns two HTML fragments: one for the page's `
` and one for its ``. The interface follows [HTML::GoogleMaps::V3](https://metacpan.org/pod/HTML%3A%3AGoogleMaps%3A%3AV3), so code written for that module needs few changes. ## Subroutines/Methods ### New Construct a new `HTML::OSM` object. ```perl my $map = HTML::OSM->new(%params); my $map = HTML::OSM->new(\%params); ``` Both method-style (`HTML::OSM->new(...)`) and function-style (`HTML::OSM::new(...)`) calls are supported. Calling `$existing_obj->new(%overrides)` performs a shallow clone, merging `%overrides` onto the existing object's state without re-validating. #### Api Specification ##### Input ```perl { cache => { type => object, can => [get, set], optional }, cluster => { type => boolean, optional }, cluster_css_url => { type => string, optional }, cluster_default_css_url => { type => string, optional }, cluster_js_url => { type => string, optional }, config_file => { type => string, optional }, coordinates => { type => arrayref, optional }, css_url => { type => string, optional }, email => { type => string, optional }, geocoder => { type => object, can => geocode, optional }, gpx_js_url => { type => string, optional }, heatmap_js_url => { type => string, optional }, height => { type => string, optional }, host => { type => string, optional }, js_url => { type => string, optional }, logger => { type => object, optional }, min_interval => { type => number, min => 0, optional }, tile_attribution => { type => string, optional }, tile_url => { type => string, optional }, ua => { type => object, optional }, width => { type => string, optional }, zoom => { type => integer, min => 0, max => 19, optional }, } ``` ##### Output ```perl { type => object, isa => 'HTML::OSM' } ``` #### Messages ``` | Message | Meaning / Resolution | |------------------------------------------------|-----------------------------------------------| | (validation error from Params::Validate::Strict) | A param has the wrong type or is out of range | ``` #### Pseudocode ```perl 1. If $class is neither a package name nor a blessed ref, treat as function-style call: prepend $class back onto @_ and use __PACKAGE__. 2. If $class is a blessed ref (clone call): merge override params onto a shallow copy and return immediately, bypassing schema validation. 3. Validate all supplied args against the declared schema. 4. Merge config-file settings via Object::Configure. 5. Resolve the cache: caller-supplied object, or in-memory CHI instance. 6. Bless and return with Readonly CDN constants as defaults. ``` ### Add\_Marker Add a point marker to the map. ```perl $map->add_marker([51.5074, -0.1278], html => 'London'); $map->add_marker('Paris, France', html => 'Paris'); $map->add_marker($geo_coder_result); ``` Returns 1 on success, 0 if the point cannot be resolved or is out of range. An empty address, or an arrayref that does not hold exactly two elements, also returns 0. #### Api Specification ##### Input ``` point : arrayref [lat, lon] | string address | object with latitude()/longitude() html : string (optional popup label) icon : string (optional icon URL) ``` ##### Output ```perl { type => integer, enum => [0, 1] } ``` #### Messages ``` | Message | Meaning / Resolution | |-----------------------------------------|---------------------------------------------| | add_marker(): unknown point type: TYPE | Point is a ref type with no lat/lon methods | | Skipping invalid coordinate: (LAT, LON) | Warning: the point is not numeric or is out of range; 0 is returned | ``` #### Examples ```perl # Coordinate array with a popup label $map->add_marker([51.5074, -0.1278], html => 'London'); # String address geocoded via the injected geocoder or Nominatim $map->add_marker('Paris, France', html => 'Paris'); # Custom icon URL with a popup $map->add_marker( [40.7128, -74.0060], html => 'New York', icon => 'https://example.com/pin.png', ); # Geo::Coder result object that implements latitude()/longitude() my $result = $geocoder->geocode('Berlin, Germany'); $map->add_marker($result, html => 'Berlin'); # Accumulate several markers, warn on geocode failure for my $city (@cities) { $map->add_marker($city->{coords}, html => $city->{name}) or warn "Could not place $city->{name}"; } ``` ### Add\_Geojson Add a GeoJSON layer to the map. ```perl $map->add_geojson(\%data, style => { color => '#ff0000' }, popup => 'name'); ``` The first argument may be a hashref/arrayref (GeoJSON structure) or a JSON string. Returns 1 on success. #### Api Specification ##### Input ``` data : hashref | arrayref | string (JSON) style : hashref Leaflet path-style options (color, weight, fillColor, fillOpacity) popup : string Feature property name whose value becomes the popup text ``` ##### Output ```perl { type => integer, value => 1 } ``` #### Messages ``` | Message | Meaning / Resolution | |----------------------|---------------------------------| | (JSON parse error) | data string is not valid JSON | ``` #### Examples ```perl # Pre-parsed GeoJSON structure with style and popup property $map->add_geojson( { type => 'FeatureCollection', features => \@features }, style => { color => '#ff0000', weight => 2, fillOpacity => 0.4 }, popup => 'name', ); # Raw JSON string - decoded automatically $map->add_geojson('{"type":"FeatureCollection","features":[]}'); # Multiple GeoJSON layers with different styles on the same map $map->add_geojson(\%country_borders, style => { color => '#333333', fillOpacity => 0 }); $map->add_geojson(\%river_data, style => { color => '#0099ff', weight => 1 }); ``` ### Add\_Heatmap Add a heatmap layer to the map. ```perl $map->add_heatmap([[51.5, -0.1, 0.8], [51.6, -0.2, 0.5]], radius => 25); ``` Each point is `[$lat, $lon]` or `[$lat, $lon, $intensity]` (intensity: 0-1). Requires the Leaflet.heat plugin (`heatmap_js_url`). Returns 1 on success. #### Api Specification ##### Input ``` points : arrayref of ([lat, lon] | [lat, lon, intensity]) radius : number default 25 (a value that is not a positive number uses the default) blur : number default 15 (a value that is not a positive number uses the default) ``` ##### Output ```perl { type => integer, value => 1 } ``` #### Messages ``` | Message | Meaning / Resolution | |-----------------------------------------|-----------------------------------| | add_heatmap: points must be an arrayref | First argument is not an arrayref | ``` #### Examples ```perl # Basic heatmap - [lat, lon] per point $map->add_heatmap([ [51.5074, -0.1278], [51.6000, -0.2000], [51.4000, 0.0000], ]); # With intensity values (0..1) and custom radius/blur $map->add_heatmap( [ [51.5, -0.1, 0.9], [51.6, -0.2, 0.5], [51.4, 0.0, 0.2] ], radius => 30, blur => 20, ); ``` ### Add\_Gpx Add a GPX track to the map from a URL. ``` $map->add_gpx('https://example.com/track.gpx'); ``` The map view is auto-fitted to the track bounds after loading. Requires the leaflet-gpx plugin (`gpx_js_url`). Returns 1 on success. #### Api Specification ##### Input ``` url : string URL of the GPX file (required) ``` ##### Output ```perl { type => integer, value => 1 } ``` #### Messages ``` | Message | Meaning / Resolution | |--------------------------|--------------------------| | add_gpx: url is required | No URL argument supplied | ``` #### Examples ``` # Add a GPX track from a public URL; the map auto-fits to its bounds $map->add_gpx('https://example.com/route.gpx'); # Multiple tracks on the same map $map->add_gpx('https://example.com/morning-run.gpx'); $map->add_gpx('https://example.com/evening-walk.gpx'); ``` ### Add\_Choropleth Add a choropleth (data-driven colour fill) layer to the map. ```perl $map->add_choropleth( \@geojson_features, { England => 100, Scotland => 80, Wales => 60 }, key => 'name', scale => ['#ffffcc', '#a1dab4', '#41b6c4', '#2c7fb8', '#253494'], ); ``` Colours are pre-computed in Perl and baked into the emitted JavaScript. No extra browser plugin is required. Returns 1 on success. #### Api Specification ##### Input ```perl features : arrayref of GeoJSON Feature hashrefs (required) values : hashref { feature_property_value => numeric_value } (required) key : string feature property to match against values (default: 'name') scale : arrayref hex-colour strings low-to-high (default: 5-step YlGnBu) ``` ##### Output ```perl { type => integer, value => 1 } ``` #### Messages ``` | Message | Meaning / Resolution | |---------------------------------------------|-----------------------------------| | add_choropleth: features must be an arrayref | First argument is not an arrayref | | add_choropleth: values must be a hashref | Second argument is not a hashref | ``` #### Examples ```perl # Default 5-step YlGnBu scale, key property "name" $map->add_choropleth( \@geojson_features, { England => 100, Scotland => 80, Wales => 60 }, ); # Custom 3-step scale and a different GeoJSON property as the key $map->add_choropleth( \@geojson_features, { England => 100, Scotland => 80, Wales => 60 }, key => 'country', scale => ['#f7fbff', '#6baed6', '#08519c'], ); # choropleth-only map - center() must be called explicitly $map->center([54.0, -2.0]); $map->add_choropleth(\@uk_features, \%population_by_region); my ($head, $body) = $map->onload_render(); ``` ### Center Set the map centre to a given point. ``` $map->center([40.7128, -74.0060]); $map->center($geo_object); $map->center('Berlin, Germany'); ``` Returns 1 on success, 0 if the point cannot be resolved or is out of range. An empty address also returns 0. #### Api Specification ##### Input ``` point : arrayref [lat, lon] | object with latitude()/longitude() | string address ``` ##### Output ```perl { type => integer, enum => [0, 1] } ``` #### Messages ```perl | Message | Meaning / Resolution | |----------------------------------------------------|---------------------------------| | center(): usage: point => [ latitude, longitude ] | No point argument supplied | | center(): point must have latitude and longitude | Arrayref has != 2 elements | | center(): unknown point type: TYPE | Ref type has no lat/lon methods | | Skipping invalid coordinate: (LAT, LON) | Warning: point is not numeric or is out of range; 0 is returned | ``` #### Examples ```perl # Coordinate array $map->center([40.7128, -74.0060]); # Object that implements latitude()/longitude() (e.g. a Geo::Coder result) $map->center($geocoder->geocode('Berlin, Germany')); # String address resolved via the injected geocoder or Nominatim $map->center('Eiffel Tower, Paris, France'); # Required when rendering without point markers (GeoJSON-only, choropleth, etc.) $map->center([54.0, -2.0]); $map->add_geojson(\%uk_regions, popup => 'name'); my ($head, $body) = $map->onload_render(); ``` ### Zoom Get or set the zoom level (0 = world, 19 = building). ```perl $map->zoom(10); my $z = $map->zoom(); ``` #### Api Specification ##### Input ```perl { zoom => { type => integer, min => 0, max => 19, optional => 1 } } ``` ##### Output ```perl { type => integer, min => 0, max => 19 } ``` #### Messages ``` | Message | Meaning / Resolution | |------------------------------|-------------------------------------------| | (Params::Validate::Strict) | zoom is not an integer or is out of range | ``` #### Examples ```perl # Setter: store the zoom level $map->zoom(14); # Getter: retrieve the current level my $level = $map->zoom(); print "Current zoom: $level\n"; # 14 # Setter return value equals the new level my $confirmed = $map->zoom(10); die 'unexpected' unless $confirmed == 10; # Chain: set via new(), read back via zoom() my $m = HTML::OSM->new(zoom => 6); $m->zoom($m->zoom() + 2); # nudge up by 2 ``` ### Onload\_Render Render the map and return a two-element list suitable for embedding in HTML. ```perl my ($head_html, $body_html) = $map->onload_render(); ``` `$head_html` contains the Leaflet CSS, JavaScript, and plugin assets. Place it inside `...`. `$body_html` contains the search box, control buttons, map `