node-red-contrib-jwht-map/README.md

740 lines
41 KiB
Markdown
Raw Normal View History

# node-red-contrib-web-worldmap
2020-04-23 02:55:27 +08:00
[![platform](https://img.shields.io/badge/platform-Node--RED-red)](https://nodered.org)
2020-04-23 05:55:53 +08:00
![NPM version](https://badge.fury.io/js/node-red-contrib-web-worldmap.svg)
2019-11-25 21:48:43 +08:00
[![GitHub license](https://img.shields.io/github/license/dceejay/redmap.svg)](https://github.com/dceejay/redmap/blob/master/LICENSE)
2019-11-03 02:03:43 +08:00
2019-03-04 16:45:56 +08:00
A <a href="https://nodered.org" target="mapinfo">Node-RED</a> node to provide a world
2016-04-01 18:31:07 +08:00
map web page for plotting "things" on.
2023-06-05 21:50:20 +08:00
Feel free to [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/dceejay) this project.
2023-06-05 21:48:18 +08:00
2016-08-05 00:09:27 +08:00
![Map Image](https://dceejay.github.io/pages/images/redmap.png)
2018-06-01 16:34:48 +08:00
### Updates
- v2.43.0 - Revert leaflet update as it broke Draw
2023-09-01 20:28:24 +08:00
- v2.42.3 - More KML and GEOJson drag drop fixes
2023-08-25 21:35:44 +08:00
- v2.42.1 - Remove extraneous debug logging, fix KMZ icons
2023-08-25 04:52:32 +08:00
- v2.42.0 - Add handling for TAK type spots, waypoints, alerts, sensors. Better KML/KMZ handling.
2023-08-14 16:24:45 +08:00
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
- v2.40.1 - Fix missing countries overlay when starting disconnected.
2023-08-11 22:21:34 +08:00
- v2.40.0 - Add handling for TAK event points from TAK ingest node.
2023-07-31 16:37:13 +08:00
- v2.39.0 - Add client timezone to connect message. PR #245
2023-07-29 23:28:03 +08:00
- v2.38.3 - Better fix for geojson multipoint icons.
2023-07-28 16:16:35 +08:00
- v2.38.1 - Fix for geojson multipoint icons.
2023-07-15 20:08:06 +08:00
- v2.38.0 - Return client headers as part of connect message.
- v2.37.4 - Fix sessionid specific data not to be sent on reload/refresh
2023-05-17 15:45:05 +08:00
- v2.37.3 - Fix hang on layer change
- v2.37.2 - If custom layer is only layer then show it automatically. Issue #230
- v2.37.1 - Warn (and drop) messages that are missing a payload. Issue #229
2023-04-11 21:56:06 +08:00
- v2.37.0 - Allow fly instead of fit option when using command to move view window. (PR #225)
- v2.36.0 - Add edge icons for SIDC markers just off the map.
2019-11-25 21:48:43 +08:00
2020-12-07 07:53:47 +08:00
- see [CHANGELOG](https://github.com/dceejay/RedMap/blob/master/CHANGELOG.md) for full list of changes.
## Install
2018-06-01 16:34:48 +08:00
Either use the Manage Palette option in the Node-RED Editor menu, or run the following command in your Node-RED user directory - typically `~/.node-red`
2016-04-01 18:31:07 +08:00
2019-09-27 19:21:59 +08:00
npm i node-red-contrib-web-worldmap
2018-06-01 06:02:01 +08:00
2018-06-01 21:04:06 +08:00
## Usage
2016-04-01 18:31:07 +08:00
2019-02-17 01:40:28 +08:00
Plots "things" on a map. By default the map will be served from `{httpRoot}/worldmap`, but this
2023-07-16 17:08:00 +08:00
can be changed in the configuration panel.
2016-04-01 18:31:07 +08:00
2018-06-01 16:34:48 +08:00
Use keyboard shortcut `⌘⇧m`, `ctrl-shift-m` to jump to the map.
2019-03-04 16:45:56 +08:00
The minimum **msg.payload** must contain `name`, `lat` and `lon` properties, for example
2016-04-01 18:31:07 +08:00
2019-02-17 01:40:28 +08:00
msg.payload = { "name":"Jason", "lat":51.05, "lon":-1.35 }
2016-04-01 18:31:07 +08:00
2018-10-26 05:12:29 +08:00
`name` must be a unique identifier across the whole map. Repeated location updates to the same `name` move the marker.
2016-04-01 18:31:07 +08:00
Optional properties include
2018-06-01 16:34:48 +08:00
- **deleted** : set to <i>true</i> to remove the named marker. (default <i>false</i>)
2023-07-16 17:08:00 +08:00
- **draggable** : set to <i>true</i> to allow marker to be moved by the mouse. (default <i>false</i>)
2019-05-30 02:22:29 +08:00
- **layer** : specify a layer on the map to add marker to. (default <i>"unknown"</i>)
- **track | hdg | heading | bearing** : when combined with speed, draws a vector. (only first will be used)
2023-07-16 17:08:00 +08:00
- **speed** : when combined with track, hdg, heading, or bearing, draws a leader line vector.
- **accuracy** : when combined with heading vector, draws an arc of possible direction.
- **color** : CSS color name or #rrggbb value for heading vector line or accuracy polygon.
- **icon** : <a href="https://fontawesome.com/v4.7.0/icons/" target="mapinfo">font awesome</a> icon name, <a href="https://github.com/Paul-Reed/weather-icons-lite" target="mapinfo">weather-lite</a> icon, :emoji name:, or https:// uri.
2018-01-03 16:31:02 +08:00
- **iconColor** : Standard CSS colour name or #rrggbb hex value.
2019-02-17 01:40:28 +08:00
- **SIDC** : NATO symbology code (can be used instead of icon). See below.
- **building** : OSMbulding GeoJSON feature set to add 2.5D buildings to buildings layer. See below.
2019-02-07 18:58:49 +08:00
- **ttl** : time to live, how long an individual marker stays on map in seconds (overrides general maxage setting, minimum 20 seconds)
- **photoUrl** : adds an image pointed at by the url to the popup box.
2017-12-14 06:16:09 +08:00
- **videoUrl** : adds an mp4 video pointed at by the url to the popup box. Ideally 320x240 in size.
- **weblink** : adds a link to an external page. Either set a url as a *string*, or an *object* like `{"name":"BBC News", "url":"https://news.bbc.co.uk", "target":"_new"}`, or multiple links with an *array of objects* `[{"name":"BBC News", "url":"https://news.bbc.co.uk", "target":"_new"},{"name":"node-red", "url":"https://nodered.org", "target":"_new"}]`
2019-02-17 01:40:28 +08:00
- **addtoheatmap** : set to <i>false</i> to exclude point from contributing to the heatmap layer. (default true)
- **intensity** : set to a value of 0.1 - 1.0 to set the intensity of the point on the heatmap layer. (default 1.0)
- **clickable** : Default true. Setting to false disables showing any popup.
2018-11-09 19:16:45 +08:00
- **popped** : set to true to automatically open the popup info box, set to false to close it.
2019-07-01 01:30:24 +08:00
- **popup** : html to fill the popup if you don't want the automatic default of the properties list. Using this overrides photourl, videourl and weblink options.
2019-03-10 22:23:53 +08:00
- **label** : displays the contents as a permanent label next to the marker, or
- **tooltip** : displays the contents when you hover over the marker. (Mutually exclusive with label. Label has priority)
- **contextmenu** : an html fragment to display on right click of marker - defaults to delete marker. You can specify `$name` to pass in the name of the marker. Set to `""` to disable just this instance.
2017-04-20 20:44:19 +08:00
2019-07-01 01:30:24 +08:00
Any other `msg.payload` properties will be added to the icon popup text box. This can be
overridden by using the **popup** property to supply your own html content. If you use the
popup property it will completely replace the contents so photourl, videourl and weblink are
meaningless in this mode.
2016-04-01 18:31:07 +08:00
### Icons
2018-10-09 00:41:03 +08:00
You may select any of the Font Awesome set of [icons](https://fontawesome.com/v4.7.0/icons/).
2019-04-12 16:50:55 +08:00
If you use the name without the fa- prefix (eg `male`) you will get the icon inside a generic marker shape. If you use the fa- prefix (eg `fa-male`) you will get the icon on its own. Likewise you can use any of the [Weather-lite](https://github.com/Paul-Reed/weather-icons-lite) icons by using the wi- prefix. These map to icons returned by common weather API such as DarkSky and OpenWeatherMap - for example `"wi-owm-"+msg.payload.weather[0].icon` will pickup the icon returned from the OpenWeatherMap API.
2019-02-12 00:40:34 +08:00
You can also specify an emoji as the icon by using the :emoji name: syntax - for example `:smile:`. Here is a **[list of emojis](https://github.com/dceejay/RedMap/blob/master/emojilist.md)**.
2023-07-16 17:08:00 +08:00
Or you can specify an image to load as an icon by setting the icon to http(s)://... By default it will be scaled to 32x32 pixels. You can change the size by setting **iconSize** to a number - eg 64. Example icon - `"https://img.icons8.com/windows/32/000000/bird.png"`
2019-03-03 07:21:47 +08:00
There are also several special icons...
2016-04-01 18:31:07 +08:00
2021-10-18 00:12:05 +08:00
- **plane** : a jet plane icon that aligns with the heading of travel.
- **smallplane** : a light aircraft icon that aligns with the heading of travel.
- **ship** : a ship icon that aligns with the heading of travel.
- **car** : a car icon that aligns with the heading of travel.
- **bus** : a bus/coach icon that aligns with the heading of travel.
2021-10-18 00:12:05 +08:00
- **uav** : a small uav like icon that aligns with the heading of travel.
- **helicopter** : a small helicopter icon that aligns with the heading of travel.
- **arrow** : a map GPS arrow type pointer that aligns with the heading of travel.
2017-06-26 23:21:55 +08:00
- **wind** : a wind arrow that points in the direction the wind is coming FROM.
- **satellite** : a small satellite icon.
2018-07-16 22:52:33 +08:00
- **iss** : a slightly larger icon for the ISS.
- **locate** : a 4 corner outline to locate a point without obscuring it.
2023-07-16 17:08:00 +08:00
- **friend** : pseudo NATO style blue rectangle. (but see NATO SIDC option below)
2018-05-18 16:19:47 +08:00
- **hostile** : pseudo NATO style red circle.
- **neutral** : pseudo NATO style green square.
- **unknown** : pseudo NATO style yellow square.
- **earthquake** : black circle - diameter proportional to `msg.mag`.
2016-04-01 18:31:07 +08:00
2018-06-01 00:32:29 +08:00
#### NATO Symbology
2018-06-02 01:49:08 +08:00
You can use NATO symbols from <a href="https://github.com/spatialillusions/milsymbol" target="mapinfo">milsymbol.js</a>.
2021-10-18 00:12:05 +08:00
To do this you need to supply a `msg.payload.SIDC` 2525 code instead of an icon, for example:
2018-06-01 00:32:29 +08:00
2020-11-19 19:48:54 +08:00
msg.payload = {
"name": "Emergency Medical Operation",
"lat": 51.05,
"lon": -1.35,
"SIDC": "ENOPA-------",
2020-11-19 19:48:54 +08:00
"options": { "fillOpacity":0.8, "additionalInformation":"Extra info" }
2018-06-01 00:32:29 +08:00
}
2023-08-11 22:21:34 +08:00
SIDC codes can be generated using the online tool - https://www.spatialillusions.com/unitgenerator-legacy/
2018-06-01 21:04:06 +08:00
2020-11-19 19:48:54 +08:00
There are lots of extra options you can specify as `msg.payload.options` - see the <a href="https://spatialillusions.com/milsymbol/documentation.html" target="mapinfo">milsymbol docs here</a>.
2023-08-11 22:21:34 +08:00
#### TAK Visualisation
Users of [TAK](https://tak.gov) can use the [TAK ingest node](https://flows.nodered.org/node/node-red-contrib-tak-registration) to create a JSON formatted TAK event object, received from a TAK server. This can be fed directly into the worldmap node.
![Tak Flow](https://github.com/dceejay/pages/blob/master/TAKflow.png?raw=true)
![Tak Image](https://github.com/dceejay/pages/blob/master/TAKicons.png?raw=true)
### Areas, Rectangles, Lines, and GreatCircles
2019-02-17 01:40:28 +08:00
If the msg.payload contains an **area** property - that is an array of co-ordinates, e.g.
2016-04-01 18:31:07 +08:00
msg.payload = {"name": "zone1", "area": [ [51.05, -0.08], [51.5, -1], [51.2, -0.047] ]}
2016-04-01 18:31:07 +08:00
then rather than draw a point and icon it draws the polygon. If the "area" array only has 2
elements, then it assumes this is a bounding box for a rectangle and draws a rectangle.
Likewise if it contains a **line** property it will draw the polyline.
2023-07-16 17:08:00 +08:00
If the payload also includes a property `fit:true` the map will zoom to fit the line or area. Alternatively you can use `fly:true` instead of fit for a more animated look.
Finally if a **greatcircle** property is set containing an array of two coordinates then an arc
following the great circle between the two co-ordinates is plotted.
2022-01-03 00:02:02 +08:00
msg.payload = {name:"GC1", color:"#ff00ff", greatcircle:[ [51.464,0], [25.76,-80.18] ] }
Shapes can also have a **popup** property containing html, but you MUST also set a property `clickable:true` in order to allow it to be seen.
There are extra optional properties you can specify - see Options below.
2016-04-01 18:31:07 +08:00
### Circles and Ellipses
2016-09-06 20:47:58 +08:00
2019-02-17 01:40:28 +08:00
If the msg.payload contains a **radius** property, as well as name, lat and lon, then rather
than draw a point it will draw a circle. The *radius* property is specified in meters.
2016-09-06 20:47:58 +08:00
msg.payload = { "name":"A3090", "lat":51.05, "lon":-1.35, "radius":3000 }
2018-05-18 16:19:47 +08:00
As per Areas and Lines you may also specify *color*, *fillColor*, and *layer*, see Options section below.
msg.payload = {
"name": "circle",
"lat": 51.515,
"lon": -0.1235,
"radius": 1000,
"layer": "drawing",
"iconColor": '#464646',
"stroke": false,
"fillOpacity": 0.8,
"clickable": true
};
2016-09-06 20:47:58 +08:00
If the **radius** property is an array of two numbers, these specify the minor and major radii
of an ellipse, in meters. A **tilt** property can also be applied to rotate the ellipse by
a number of degrees.
msg.payload = { "name":"Bristol Channel", "lat":51.5, "lon":-2.9, "radius":[30000,70000], "tilt":45 };
2019-11-25 21:48:43 +08:00
2020-05-29 20:34:57 +08:00
### Arcs, Range Rings
You can add supplemental arc(s) to a marker by adding an **arc** property as below.
Supplemental means that you can also specify a line using a **bearing** and **length** property.
```
msg.payload = { name:"Camera01", icon:"fa-camera", lat:51.05, lon:-1.35,
bearing: 235,
length: 2200,
arc: {
ranges: [500,1000,2000],
pan: 228,
fov: 40,
color: '#aaaa00',
weight: 1
2020-05-29 20:34:57 +08:00
}
}
```
**ranges** can be a single number or an array of arc distances from the marker.
The **pan** is the bearing of the centre of the arc, and the **fov** (Field of view)
specifies the angle of the arc.
Defaults are shown above.
2020-03-24 19:53:54 +08:00
### GeoJSON
2020-05-29 20:34:57 +08:00
If the msg.payload contains a **geojson** property, and no **lat** and **lon**, then
rather than draw a point it will render the geojson.
2020-05-06 06:37:27 +08:00
msg.payload = {
"name": "MyPolygon",
"geojson": {
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[-180,10],[20,90],[180,-5],[-30,-90]]]
},
"style": {
"stroke-width": "8",
"stroke": "#ff00ff",
"fill-color": "#808000",
"fill-opacity": 0.2
}
}
}
2020-05-29 20:34:57 +08:00
Often geojson may not have a `properties` or `style` property in which case you can specify some global optional properties (see below) in order to set some defaults for the geojson object.
2020-05-06 06:37:27 +08:00
msg.payload = {
"name": "Myline",
2020-05-29 20:34:57 +08:00
"layer": "Lines",
2020-05-06 06:37:27 +08:00
"color": "#0000ff",
"weight": "6",
"dashArray": "30 20",
"geojson": {
"type": "LineString",
"coordinates": [[0,0],[0,90]]
},
clickable: true
2020-05-06 06:37:27 +08:00
}
2020-03-24 19:53:54 +08:00
2020-05-29 20:34:57 +08:00
**Note**: you can just send a msg.payload containing the geojson itself - but obviously you then can't style
it, set the name, layer, etc.
2020-03-24 19:53:54 +08:00
### Options
Areas, Rectangles, Lines, Circles and Ellipses can also specify more optional properties:
- **layer** : declares which layer you put it on.
- **color** : can set the colour of the polygon or line.
2021-12-21 23:44:56 +08:00
- **opacity** : the opacity of the line or outline.
- **fillColor** : can set the fill colour of the polygon.
- **fillOpacity** : can set the opacity of the polygon fill colour.
- **dashArray** : optional dash array for polyline.
- **clickable** : boolean - set to true to allow click to show popup.
- **popup** : html string to display in popup (as well as name).
2021-12-21 23:44:56 +08:00
- **editable** : boolean - set to true to allow simple edit/delete right click contextmenu.
- **contextmenu** : html string to display a more complex right click contextmenu.
- **weight** : the width of the line or outline.
Other properties can be found in the leaflet documentation.
Shapes can also have a **popup** property containing html, but you MUST also set a property `clickable:true` in order to allow it to be seen.
2019-11-25 21:48:43 +08:00
2020-05-29 20:34:57 +08:00
### Drawing
2019-11-25 21:48:43 +08:00
A single *right click* will allow you to add a point to the map - you must specify the `name` and optionally the `icon` and `layer`.
2019-11-25 21:48:43 +08:00
2020-05-29 20:34:57 +08:00
Right-clicking on an icon will allow you to delete it.
If you select the **drawing** layer you can also add and edit polylines, polygons, rectangles and circles.
Once an item is drawn you can right click to edit or delete it. Double click the object to exit edit mode.
### Buildings
The OSM Buildings layer is available in the layers menu. You can replace this with
buildings of your own by sending a `msg.payload.command.map` containing an `overlay`
and a `geojson` property. The geojson property should be a GeoJSON Feature Collection
as per the OSMBuildings spec. For example in a function node:
var geo = { "type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"color": "rgb(0,0,255)",
"roofColor": "rgb(128,128,255)",
"height": 20,
"minHeight": 0
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-1.356221,51.048611],
[-1.356039,51.048672],
[-1.355765,51.048311],
[-1.355937,51.048237],
[-1.356221,51.048611]
]
]
}
}
]
2019-11-25 21:48:43 +08:00
}
2020-05-29 20:34:57 +08:00
var m = {overlay:"Golf Clubhouse", geojson:geo, fit:true};
msg.payload = {command:{map:m, lat:51.0484, lon:-1.3558}};
return msg;
2019-11-25 21:48:43 +08:00
2020-05-29 20:34:57 +08:00
**Note**: the object you supply will replace the whole buildings layer. To delete the building send a msg with a name and the building property set to "" (blank string).
2019-11-25 21:48:43 +08:00
2020-05-29 20:34:57 +08:00
#### Buildings 3D view
2019-11-25 21:48:43 +08:00
2020-05-29 20:34:57 +08:00
A 3D map view has now been added as **worldmap/index3d.html** using the mapbox api - the msg can support `msg.command.pitch` and `msg.command.bearing` to angle the view, for example:
2016-04-08 04:35:43 +08:00
2020-05-29 20:34:57 +08:00
msg.payload = { "command": { "zoom":18, "pitch":60, "bearing":80 } }
2016-04-08 04:35:43 +08:00
2020-05-29 20:34:57 +08:00
The `icon` can be specified as a person, block, bar, or "anything else" - they will render slightly differently - all units are approximate. They will be positioned at the `lat`, `lon` as normal but also at the `msg.payload.height` - where height is in meters above the surface of the map (which may or may not relate to altitude...)
2016-04-08 04:35:43 +08:00
2020-05-29 20:34:57 +08:00
`msg.payload.icon` can be
- person : 1m x 1m x 2m tall
- block : 5m x 5m x 5m cube
- bar : a bar from the surface up to the specified minHeight
- (else) : 1.5m x 1.5m x 1.5m cube
in addition existing male, female, fa-male and fa-female icons are all represented as the person shape.
`msg.iconColor` can be used to colour the icons.
**NOTES**
2023-07-16 17:08:00 +08:00
- There is currently no way to add labels, popups, or to make the icons clickable.
2020-05-29 20:34:57 +08:00
- The 3D only really works at zoomed in scales 16+ due to the small size of the icons. They are not scale independent like icons on the normal map.
- As this uses the mapbox api you may wish to edit the index3d.html code to include your api key to remove any usage restrictions.
- This view is a side project to the Node-RED Worldmap project so I'm happy to take PRs but it probably won't be actively developed.
2016-04-08 04:35:43 +08:00
2019-11-25 21:48:43 +08:00
2019-02-17 01:40:28 +08:00
## Events from the map
The **worldmap in** node can be used to receive various events from the map. Examples of messages coming FROM the map include:
2019-02-17 01:58:41 +08:00
{ "action": "connected" } // useful to trigger delivery or redraw of points
{ "action": "disconnect", "clients": 1 } // when a client disconnects - reports number remaining
{"action":"bounds", "south":50.55, "west":-1.48, "north":50.72, "east":-0.98} // reports the outer bounds of the hmap area when zoomed or moved
2019-02-17 01:58:41 +08:00
{ "action": "click", "name":"Jason", "layer":"gps", "icon":"male", "iconColor":"blue", "lat":51.024985, "lon":-1.39698 } // when a marker is clicked
{ "action": "move", "name":"Jason", "layer":"gps", "icon":"male", "iconColor":"blue", "lat":51.044632, "lon":-1.359901 } // when a marker is moved
2019-02-17 17:40:26 +08:00
{ "action": "delete", "name": "Jason" } // when a point or shape is deleted
2016-04-08 04:35:43 +08:00
2019-02-17 17:40:26 +08:00
{ "action": "point", "lat": "50.60634", "lon": "-1.66580", "point": "Jason,male,gps" }
{ "action": "draw", "type": "rectangle", "points": [ { "lat": 50.61243889044519, "lng": -1.5913009643554688 }, { "lat": 50.66665471366635, "lng": -1.5913009643554688 }, { "lat": 50.66665471366635, "lng": -1.4742279052734375 }, { "lat": 50.61243889044519, "lng": -1.4742279052734375 } ] }
2019-02-17 01:40:28 +08:00
{ "action": "layer", "name": "myLayer" } // when a map layer is changed
{ "action": "addlayer", "name": "myLayer" } // when a new map layer is added
{ "action": "dellayer", "name": "myLayer" } // when a new map layer is deleted
2016-04-08 04:35:43 +08:00
{ "action": "file", "name": "myfilename", "type":"image/jpeg", "lat":51, "lon":-1, "content":"....."} // when a file is dropped on the map - see below.
2020-12-07 07:53:47 +08:00
{ "action": "button", "name": "My Fancy Button" } // when a user defined button is clicked
2020-08-24 18:39:59 +08:00
{ "action": "feedback", "name": "some name", "value": "some value", "lat":51, "lon":0, "layer":"unknown" } // when a user calls the feedback function - see below
2019-03-10 22:23:53 +08:00
If File Drop is enabled - then the map can accept files of type gpx, kml, nvg, jpeg, png and geojson. The file content property will always be a binary buffer. The lat, lon of the cursor drop point will be included. Tracks will be locally rendered on the map. The node-red-node-exif node can be used to extract location information from a jpeg image and then geolocate it back on the map. Png images will be located where they are dropped but can then be dragged if required.
2020-12-07 07:53:47 +08:00
All actions also include a:
`msg._sessionid` property that indicates which client session they came from. Any msg sent out that includes this property will ONLY be sent to that session - so you can target map updates to specific sessions if required.
2023-07-16 17:08:00 +08:00
`msg._sessionip` property that shows the ip of the client that is connected to the session.
The "connected" action additionally includes a:
`msg.payload.parameters` property object that lists the parameters sent in the url.
`msg.payload.clientTimezone` property string showing the clients local Timezone. Returns bool of `false` if unable to retrive clients local Timezone.
`msg._clientheaders` property that shows the headers sent by the client to make a connection to the session.
2019-02-17 01:58:41 +08:00
### Utility functions
There are some internal functions available to make interacting with Node-RED easier (e.g. from inside a user defined popup., these include:
- **feedback()** : it takes 2, 3, or 4 parameters, name, value, and optionally an action name (defaults to "feedback"), and optional boolean to close the popup on calling this function, and can be used inside something like an input tag - `onchange='feedback(this.name,this.value,null,true)'`. Value can be a more complex object if required as long as it is serialisable. If used with a marker the name should be that of the marker - you can use `$name` to let it be substituted automatically.
2021-02-20 23:42:55 +08:00
- **addToForm()** : takes a property name value pair to add to a variable called form. When used with contextmenu feedback (above) you can set the feedback value to `"$form"` to substitute this accumulated value. This allows you to do things like `onChange='addToForm(this.name,this.value)'` over several different fields in the menu and then use `feedback(this.name,"$form")` to submit them all at once. For example a simple multiple line form could be:
2021-02-21 20:06:34 +08:00
```
var menu = 'Add some data <input name="foo" onchange=\'addToForm(this.name,this.value)\'></input><br/>'
menu += 'Add more data <input name="bar" onchange=\'addToForm(this.name,this.value)\'></input><br/>'
2021-02-20 23:42:55 +08:00
menu += '<button name="my_form" onclick=\'feedback(this.name,"$form",null,true)\'>Submit</button>'
msg.payload = { command: { "contextmenu":menu } }
```
- **delMarker()** : takes the name of the marker as a parameter. In a popup this can be specified as `$name` for dynamic substitution.
- **editPoly()** : takes the name of the shape or line as a parameter. In a popup this can be specified as `$name` for dynamic substitution.
2019-02-17 01:40:28 +08:00
## Controlling the map
2016-04-01 18:31:07 +08:00
You can also control the map via the node, by sending in a msg.payload containing a **command** object. Multiple parameters can be specified in one command.
2016-04-01 18:31:07 +08:00
Optional properties include
- **lat** - move map to specified latitude.
- **lon** - move map to specified longitude.
- **zoom** - move map to specified zoom level (1 - world, 13 to 20 max zoom depending on map).
2022-01-16 23:35:27 +08:00
- **bounds** - if set to an array `[ [ lat(S), lon(W) ], [lat(N), lon(E)] ]` - sets the overall map bounds.
2019-02-17 01:40:28 +08:00
- **layer** - set map to specified base layer name - `{"command":{"layer":"Esri"}}`
- **search** - search markers on map for name containing `string`. If not found in existing markers, will then try geocoding looking using Nominatim. An empty string `""` clears the search results. - `{"command":{"search":"Winchester"}}`
- **showlayer** - show the named overlay(s) - `{"command":{"showlayer":"foo"}}` or `{"command":{"showlayer":["foo","bar"]}}`
- **hidelayer** - hide the named overlay(s) - `{"command":{"hidelayer":"bar"}}` or `{"command":{"hidelayer":["bar","another"]}}`
2019-02-17 01:40:28 +08:00
- **side** - add a second map alongside with slide between them. Use the name of a *baselayer* to add - or "none" to remove the control. - `{"command":{"side":"Esri Satellite"}}`
- **split** - once you have split the screen - the split value is the % across the screen of the split line. - `{"command":{"split":50}}`
2016-04-01 18:31:07 +08:00
- **map** - Object containing details of a new map layer:
- **name** - name of the map base layer OR **overlay** - name of overlay layer
2016-04-01 18:31:07 +08:00
- **url** - url of the map layer
- **opt** - options object for the new layer
2019-03-30 02:26:55 +08:00
- **wms** - true/false/grey, specifies if the data is provided by a Web Map Service (if grey sets layer to greyscale)
2019-02-17 01:40:28 +08:00
- **bounds** - sets the bounds of an Overlay-Image. 2 Dimensional Array that defines the top-left and bottom-right Corners (lat/lon Points)
- **delete** - name or array of names of base layers and/or overlays to delete and remove from layer menu.
2022-12-13 23:45:27 +08:00
- **heatmap** - set heatmap latlngs array object see https://github.com/Leaflet/Leaflet.heat#reference
- **options** - if heatmap set, then use this to set heatmap options object see https://github.com/Leaflet/Leaflet.heat#reference
- **clear** - layer name - to clear a complete layer and remove from layer menu - `{"command":{"clear":"myOldLayer"}}`
2019-02-17 01:40:28 +08:00
- **panlock** - lock the map area to the current visible area. - `{"command":{"panlock":true}}`
- **panit** - auto pan to the latest marker updated. - `{"command":{"panit":true}}`
2019-02-17 01:40:28 +08:00
- **zoomlock** - locks the zoom control to the current value and removes zoom control - `{"command":{"zoomlock":true}}`
2019-02-28 06:05:02 +08:00
- **hiderightclick** - disables the right click that allows adding or deleting points on the map - `{"command":{"hiderightclick":true}}`
2019-03-11 02:29:44 +08:00
- **coords** - turns on and off a display of the current mouse co-ordinates. Values can be "deg", "dms", or "none" (default). - `{"command":{"coords":"deg"}}`
- **showruler** - turns on and off a display of the ruler control. Values can be "true" or "false". - `{"command": {"ruler": {"showruler": true}}}`
- **button** - if supplied with a `name` and `icon` property - adds a button to provide user input - sends
2022-06-09 02:18:11 +08:00
a msg `{"action":"button", "name":"the_button_name"}` to the worldmap in node. If supplied with a `name` property only, it will remove the button. Optional `position` property can be 'bottomright', 'bottomleft', 'topleft' or 'topright' (default). button can also be an array of button objects.
- **contextmenu** - html string to define the right click menu when not on a marker. Defaults to the simple add marker input. Empty string `""` disables this right click.
- **toptitle** - Words to replace title in title bar (if not in iframe)
- **toplogo** - URL to logo image for top tile bar (if not in iframe) - ideally 60px by 24px.
- **trackme** - Turns on/off the browser self locating. Boolean false = off, true = cyan circle showing accuracy error, or an object like `{"command":{"trackme":{"name":"Dave","icon":"car","iconColor":"blue","layer":"mytrack","accuracy":false}}}`. Usual marker options can be applied.
- **showmenu** - Show or hide the display of the hamberger menu control in the top right . Values can be "show" or "hide". - `{"command":{"showmenu": "hide"}}`
- **showlayers** - Show or hide the display of selectable layers. Does not control the display of an individual layer, rather a users ability to interact with them. Values can be "show" or "hide". - `{"command":{"showlayers": "hide"}}`
2016-04-01 18:31:07 +08:00
2018-05-18 16:31:35 +08:00
#### To switch layer, move map and zoom
2016-04-01 18:31:07 +08:00
2019-03-12 01:33:53 +08:00
msg.payload = { "command": { "layer":"Esri Satellite", "lat":51, "lon":3, "zoom":10 }};
You can also use the name "none" to completely remove the base layer,
msg.payload = { "command": { "layer":"none" }};
2016-04-01 18:31:07 +08:00
#### To add and remove a user defined button
to add a button bottom right
msg.payload.command = { "button": { "name":"My Fancy Button", "icon": "fa-star", "position":"bottomright" } };
When clicked the button will send an event to the `worldmap in` node containing `{"action":"button", "name","My Fancy Button"}` - this can then be used to trigger other map commands or flows.
to remove
msg.payload.command = { "button": { "name":"My Fancy Button" } };
2022-06-09 02:18:11 +08:00
Multiple buttons can declared by using an array of button objects.
#### To add a custom popup or contextmenu
You can customise a marker's popup, or context menu (right click), by setting the
appropriate property to an html string. Often you will need some embedded javascript
in order to make it do something when you click a button for example. You need to be
careful escaping quotes, and that they remain matched.
For example a marker popup with a slider (note the \ escaping the internal ' )
popup: '<input name="slide1" type="range" min="1" max="100" value="50" onchange=\'feedback($name,this.value,this.name)\' style="width:250px;">'
Or a marker contextmenu with an input box
contextmenu: '<input name="channel" type="text" value="5" onchange=\'feedback($name,{"name":this.name,"value":this.value},"myFeedback")\' />'
Or you can add a contextmenu to the map. Simple contextmenu with a button
msg.payload.command = {
2021-02-20 00:09:50 +08:00
contextmenu: '<button name="Clicker" onclick=\'feedback(this.name,"ping!",null,true)\'>Click me</button>'
}
Or with an input box
msg.payload.command : {
contextmenu: '<input name="slide1" type="range" min="1" max="100" value="50" onchange=\'feedback(this.name,this.value,"myEventName")\' >'
}
Example simple form
```
[{"id":"7351100bacb1f5fe","type":"function","z":"4aa2ed2fd1b11362","name":"","func":"msg.payload = { command: {\ncontextmenu: String.raw`\nText <input type=\"text\" id=\"sometext\" value=\"hello\"><br/>\nNumber <input type=\"number\" id=\"somenum\" value=\"5\"><br/>\n<input type=\"button\" value=\"Send\" onclick=\n'feedback(\"myform\",{\n \"st\":document.getElementById(\"sometext\").value,\n \"sn\":document.getElementById(\"somenum\").value,\n})'\n>\n`\n}}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":360,"wires":[["a6a82f2e8efc44fc"]]},{"id":"7b595f0c8f6ac710","type":"worldmap in","z":"4aa2ed2fd1b11362","name":"","path":"/worldmap","events":"connect","x":195,"y":360,"wires":[["7351100bacb1f5fe"]]}]
```
See the section on **Utility Functions** for details of the feedback function.
2019-11-13 07:56:50 +08:00
#### To add and remove a legend
If you want to add a small legend overlay
msg.payload.command = { "legend": "<b>Title</b></br><i style=\"background: #477AC2\"></i> Water<br><i style=\"background: #448D40\"></i> Forest<br>" };
2019-11-17 06:04:05 +08:00
To remove set the legend to an empty string `""`.
2018-05-18 16:31:35 +08:00
#### To add a new base layer
2016-04-01 18:31:07 +08:00
The layer will be called `name`. By default it expects a leaflet Tilelayer style url. You can also use a WMS
2019-03-30 02:26:55 +08:00
style server by adding a property `wms: true`. You can also set `wms: "grey"` to set the layer to greyscale which
2023-08-13 21:06:05 +08:00
may let your markers be more visible. (see overlay example below).
2018-06-01 06:02:01 +08:00
msg.payload.command.map = {
"name":"OSMhot", // use "overlay":"MyOverlayName" for an overlay rather than a base layer.
"url":"https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
"opt":{ "maxZoom":19, "attribution":"&copy; OpenStreetMap" }
2018-06-01 06:02:01 +08:00
};
2016-04-01 18:31:07 +08:00
2020-08-03 20:16:53 +08:00
#### To add a minimap
A minimap overview can be added by sending a suitable command. The "minimap" property
must specify the name of an existing base layer to use. The "opt" property can contain
2020-08-03 20:16:53 +08:00
valid options from the [minimap library options](https://github.com/Norkart/Leaflet-MiniMap#available-options).
msg.payload.command.map = {
minimap: "OSM",
opt: {
centerFixed: [51.05, -1.35],
toggleDisplay: true
}
}
};
Set `msg.payload.command.map.minimap = false;` to remove the minimap.
2019-03-19 22:25:04 +08:00
#### To remove base or overlay layers
To remove several layers, either base layers or overlays, you can pass an array of names as follows.
This can be used to tidy up the initial selections available to the user layer menu.
2019-03-19 22:25:04 +08:00
msg.payload.command.map = {
2019-05-07 19:33:18 +08:00
"delete":["Watercolor","ship nav","heatmap","Terrain","UK OS 1900","UK OS 1919-47"]
2019-03-19 22:25:04 +08:00
};
2019-05-07 19:43:22 +08:00
Note: layer names are case sensitive.
#### To add a WMS overlay layer - eg US weather radar
To add an overlay instead of a base layer - specify the `overlay` property instead of the `name`.
msg.payload.command.map = {
"overlay": "NowCoast",
"url": "https://nowcoast.noaa.gov/arcgis/services/nowcoast/radar_meteo_imagery_nexrad_time/MapServer/WmsServer?",
"opt": {
"layers": "1",
2019-04-03 00:44:57 +08:00
"format": "image/png",
"transparent": true,
"attribution": "NOAA/NWS"
},
"wms": true
}
By default the overlay will be instantly visible. To load it hidden add a property to the command.map - `visible:false`
2018-05-18 16:31:35 +08:00
#### To add a new geoJSON overlay
2018-01-03 16:31:02 +08:00
2018-06-01 06:02:01 +08:00
msg.payload.command.map = {
"overlay": "myGeoJSON",
"geojson": { your geojson feature as an object },
"opt": { optional geojson options, style, etc },
"fit": true
"clickable": false
2018-06-01 06:02:01 +08:00
};
2018-01-03 16:31:02 +08:00
2018-09-21 05:26:17 +08:00
The geojson features may contain a `properties` property. That may also include a `style` with properties - stroke, stroke-width, stroke-opacity, fill, fill-opacity. Any other properties will be listed in the popup.
The `opt` property is optional. See the <a href="https://leafletjs.com/examples/geojson/">Leaflet geojson docs</a> for more info on possible options. Note: only simple options are supported as functions cannot be serialised.
2018-06-27 03:42:24 +08:00
2023-04-11 21:56:06 +08:00
The `fit` property is optional, and you can also use `fly` if you wish. If boolean true the map will automatically zoom to fit the area relevant to the geojson, or use the 'fly' to animation style. You can also set `clickable` true to return the properties of the clicked feature to the worldmap-in node.
2019-02-17 01:40:28 +08:00
see https://leafletjs.com/examples/geojson/ for more details about options for opt.
2018-06-27 03:42:24 +08:00
#### To add a new KML, GPX, or TOPOJSON overlay
2018-09-21 05:26:17 +08:00
As per the geojson overlay you can also inject a KML layer, GPX layer or TOPOJSON layer. The syntax is the same but with either a `kml` property containing the KML string - a `gpx` property containing a GPX string - or a `topojson` property containing the topojson.
2018-06-27 03:42:24 +08:00
msg.payload.command.map = {
"overlay": "myKML",
"kml": "<kml>...your kml placemarks...</kml>"
2018-06-27 03:42:24 +08:00
};
2018-09-21 05:26:17 +08:00
For GPX and KML layers, it is possible to define which icon to use for point markers by adding the
following properties to `msg.payload.command.map`:
2019-02-17 01:40:28 +08:00
2018-09-21 05:26:17 +08:00
- **icon** : <a href="https://fontawesome.com/v4.7.0/icons/" target="mapinfo">font awesome</a> icon name.
- **iconColor** : Standard CSS colour name or #rrggbb hex value.
2023-04-11 21:56:06 +08:00
Again the boolean `fit` or `fly` properties can be added to make the map zoom to the relevant area, and the `visible` property can be set false to not immediately show the layer.
2018-01-03 16:31:02 +08:00
2018-06-03 23:12:13 +08:00
#### To add a Velocity Grid Overlay
msg.payload.command.map = {
"overlay": "myWind",
"velocity": {
"displayValues": true,
"displayOptions": {
"velocityType": "Global Wind",
"displayPosition": "bottomleft",
"emptyString": "No wind data",
"showCardinal": true,
"speedUnit": "k/h",
"angleConvention": "meteoCCW"
2018-06-03 23:12:13 +08:00
},
"maxVelocity": 15,
"data": [Array of data as per format referenced below]
2018-06-03 23:12:13 +08:00
}
};
see https://github.com/danwild/leaflet-velocity for more details about options and data examples.
Note: If you use his wind-js-server you need to patch it as per [issue 9](https://github.com/danwild/wind-js-server/issues/9).
2021-04-06 18:28:41 +08:00
Or you can use try this docker image which uses the Canadian Meterological Service model instead.
docker run -d -p 7000:7000 --name windserver theceejay/windserver:latest
2018-05-18 16:31:35 +08:00
#### To add an Image Overlay
in a function node:
2018-06-01 06:02:01 +08:00
var imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
msg.payload = { command : { lat:40.74, lon:-74.175, zoom:13 } };
2018-06-01 06:02:01 +08:00
msg.payload.command.map = {
2019-02-17 01:40:28 +08:00
overlay: "New York Historical",
url: 'https://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
2018-06-01 06:02:01 +08:00
bounds: imageBounds,
2019-03-12 01:33:53 +08:00
opt: { opacity:0.8, attribution:"&copy; University of Texas" }
2018-06-01 06:02:01 +08:00
};
2019-04-22 05:32:01 +08:00
#### To add a Lat/Lon Graticule overlay
A graticule can be enabled via the node configuration, and can also be set dynamically,
for example in a function node:
msg.payload = { command : { grid : {
showgrid: true,
opt: { showLabel:true, dashArray:[5, 5], fontColor:"#900" }
};
see https://github.com/cloudybay/leaflet.latlng-graticule for more details about options and demo.
2019-03-12 01:33:53 +08:00
#### To clear all markers from a layer, or an overlay from the map
2018-05-18 16:19:47 +08:00
msg.payload.command.clear = "name of the layer/overlay you wish to clear";
Feeding this into the tracks node will also remove the tracks stored for that layer.
2018-05-18 16:19:47 +08:00
2017-04-20 20:44:19 +08:00
### Using a local Map Server (WMS server)
2018-06-02 01:49:08 +08:00
IMHO the easiest map server to make work is the <a href="http://www.mapserver.org/" target="mapinfo">mapserver</a> package in Ubuntu / Debian. Usually you will start with
2017-04-20 20:44:19 +08:00
sudo apt-get install mapserver-bin cgi-mapserver gdal-bin
2021-03-08 16:41:15 +08:00
Configuring that, setting up your tiles, and creating a **.map** file is way beyond the scope of this README so I will leave that as an exercise for the reader. Once set up you should have a cgi process you can run called `mapserv`, and a `.map` file that describes the layers available from the server.
2017-04-20 20:44:19 +08:00
2018-05-18 16:31:35 +08:00
Create and edit these into an executeable file called **mapserv**, located in this node's directory, typically
2017-04-20 20:44:19 +08:00
`~/.node-red/node_modules/node-red-contrib-web-worldmap/mapserv`, for example:
#! /bin/sh
# set this to the path of your WMS map file (which in turn points to your tiles)
MS_MAPFILE=/home/pi/maps/gb.map
2017-04-20 20:44:19 +08:00
export MS_MAPFILE
# and set this to the path of your cgi-mapserv executable
/usr/bin/mapserv
You can then add a new WMS Base layer by injecting a message like
msg.payload = { command : { map : {
"name": "Local WMS",
"url": "/cgi-bin/mapserv", // we will serve the tiles from this node locally.
"opt": {
"layers": "gb", // specifies a layer in your map file
"format": "image/png",
"transparent": true,
"attribution": "© Ordnance Survey, UK"
2017-04-20 20:44:19 +08:00
},
"wms": true // set to true for WMS type mapserver
}}}
2017-04-20 20:44:19 +08:00
2019-03-30 02:26:55 +08:00
#### Using a Docker Map Server
2021-03-08 23:52:09 +08:00
You can use a docker container like https://hub.docker.com/r/camptocamp/mapserver, then assuming you have the mapfile 'my-app.map' in the current working directory, you could mount it as:
```
docker run -d --name camptocamp -v $(pwd):/etc/mapserver/:ro -p 1881:80 camptocamp/mapserver
```
2021-03-08 16:41:15 +08:00
then the url should be of the form `"url": "http://localhost:1881/?map=/etc/mapserver/my-app.map"` where *my-app.map* is the name of your map file. A quick test of the server would be to browse to http://localhost:1881/?map=/etc/mapserver/my-app.map&mode=map
Or you can use a docker container like https://hub.docker.com/r/geodata/mapserver/ then assuming you have the mapfile 'my-app.map' in the current working directory, you could mount it as:
```
docker run -d --name mapserver -v $(pwd):/maps:ro -p 1882:80 geodata/mapserver
```
and use a url like `"url": "http://localhost:1882/?map=/maps/my-app.map",`
To use a vector mbtiles server like **MapTiler** then you can download your mbtiles file into a directory and then from that directory run
```
2023-08-11 22:21:34 +08:00
docker run --name maptiler -d -v $(pwd):/data -p 1884:8080 maptiler/tileserver-gl -p 8080 --mbtiles yourMapFile.mbtiles
```
and use a url like `"url": "http://localhost:1884/styles/basic-preview/{z}/{x}/{y}.png"`
Other useful map servers include Geoserver, a somewhat larger image but fully featured.
```
docker run --name geoserver -d -v ${PWD}:/var/local/geoserver -p 1885:8080 oscarfonts/geoserver
```
2021-03-08 16:41:15 +08:00
---
2017-04-20 20:44:19 +08:00
## Examples and Demo Flow
There are several examples included when you install the node. Navigate to the menu - Import - Examples - node-red-contrib-web-worldmap
2016-04-01 18:31:07 +08:00
The following example gets recent earthquakes from USGS, parses the result,
formats up the msg as per above and sends to the node to plot on the map.
2016-04-01 18:31:07 +08:00
It also shows how to zoom and move the map or add a new layer.
[{"id":"86457344.50e6b","type":"inject","z":"745a133b.dd6dec","name":"","topic":"","payload":"","payloadType":"none","repeat":"","crontab":"","once":false,"x":190,"y":2420,"wires":[["9a142026.fa47f"]]},{"id":"9a142026.fa47f","type":"function","z":"745a133b.dd6dec","name":"add new layer","func":"msg.payload = {};\nmsg.payload.command = {};\n\nvar u = 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';\nvar o = { maxZoom: 19, attribution: '&copy; OpenStreetMap'};\n\nmsg.payload.command.map = {name:\"OSMhot\", url:u, opt:o};\nmsg.payload.command.layer = \"OSMhot\";\n\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":2420,"wires":[["c643e022.1816c"]]},{"id":"c643e022.1816c","type":"worldmap","z":"745a133b.dd6dec","name":"","x":750,"y":2460,"wires":[]},{"id":"2998e233.4ba64e","type":"function","z":"745a133b.dd6dec","name":"USGS Quake monitor csv re-parse","func":"msg.payload.lat = msg.payload.latitude;\nmsg.payload.lon = msg.payload.longitude;\nmsg.payload.layer = \"earthquake\";\nmsg.payload.name = msg.payload.id;\nmsg.payload.icon = \"globe\";\nmsg.payload.iconColor = \"orange\";\n\ndelete msg.payload.latitude;\ndelete msg.payload.longitude;\t\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":2560,"wires":[["c643e022.1816c"]]},{"id":"e72c5732.9fa198","type":"function","z":"745a133b.dd6dec","name":"move and zoom","func":"msg.payload = { command:{layer:\"Esri Terrain\",lat:0,lon:0,zoom:3} };\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":2460,"wires":[["c643e022.1816c"]]},{"id":"12317723.589249","type":"csv","z":"745a133b.dd6dec","name":"","sep":",","hdrin":true,"hdrout":"","multi":"one","ret":"\\n","temp":"","x":390,"y":2500,"wires":[["2998e233.4ba64e"]]},{"id":"10e5e5f0.8daeaa","type":"inject","z":"745a133b.dd6dec","name":"","topic":"","payload":"","payloadType":"none","repeat":"","crontab":"","once":false,"x":190,"y":2460,"wires":[["e72c5732.9fa198"]]},{"id":"b6917d83.d1bac","type":"http request","z":"745a133b.dd6dec","name":"","method":"GET","url":"http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.csv","x":270,"y":2560,"wires":[["12317723.589249"]]},{"id":"3842171.4d487e8","type":"inject","z":"745a133b.dd6dec","name":"Quakes","topic":"","payload":"","payloadType":"none","repeat":"900","crontab":"","once":false,"x":200,"y":2500,"wires":[["b6917d83.d1bac"]]}]
2016-04-18 23:57:31 +08:00
2021-03-08 16:41:15 +08:00
---
2016-04-18 23:57:31 +08:00
2020-05-06 06:37:27 +08:00
Car, Bus and Helicopter icons originally made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a> are licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="mapinfo">CC 3.0 BY</a>.