mappy
v1.0.0
Published
A configurable map engine
Downloads
40
Readme
MAPPY
WARNING: This library is under heavy development and should be considered ALPHA We don't recommend using in production at this time.
Creating a map using mappy
- Clone this repo
npm install
- Rename dist/example-config.js to config.js
- Customise properties of the config object (see 'Config in detail') in config.js
npm run build
- use resulting
dist/bundle.js
anddist/bundle.min.js
files
Developing mappy
- run
npm run build:dev
to produce a non minified map bundle file - and/or run
npm run build:prod
to produce a minified map bundle file.
Installation
You can use this project to build various javascript map bundles from config.
Quickstart
- clone the repo
npm install
npm run build
- use resulting
dist/bundle.js
anddist/bundle.min.js
files
Config in detail
Configuration is a javascript object with three properties: map, layers and key
key
Defines the map key (optional)
dataSources
Defines the data sources that the layers can use. There are currently 3 options
longPoll
for refreshing the data from a URL on an intervalsinglePoll
to only load the data from a URL oncelocal
to not load any data automatically. Layer data can be added viamap.pushToDataService(dataSourceName, geojsonData)
Data source definitions should be in format:
{
dataSources: [
{
name: 'my-api',
type: 'longPoll' // longPoll, singlePoll, local
refresh: 10000 // Interval to refresh the data from the endpoint, only required for longPoll
request: { // for longPoll or singlePoll
url: '/path/to/some/geojson/endpoint'
// other options for xhr module (https://github.com/Raynos/xhr) can be passed in here e.g. headers: {}
}
}
]
}
layers
Defines an array of layers to plot on the map. Currently types geojson
and
click
are supported.
- Type
geojson
can be used to render a geojson feature collection on the map - Type
click
can be used to wire up user clicks to external services
Geojson layers
The main way that layers are loaded into mappy is via geojson. A geojson layer can be added defining a layer of type = geojson in the layers array of the config object
{
layers: [
{
name: 'my-first-layer',
type: 'geojson',
dataSource: 'my-api'
}
]
}
Assuming that valid geojson was returned, a layer of data should be plotted on the map.
Geojson layer relationships
If you have multiple layers defined for your map and you would like some layers to react to user interactions on another layer you can define pub/sub like relationships between layers.
On one layer we specify that any user actions of the specified type(s) performed on the layer should be 'notified' to any other interested layers
{
name: 'my-first-layer',
type: 'geojson',
notifies: ['click'],
//...
}
Then, on another layer we specify that we are interested in any user clicks on 'my-first-layer', registering a handler like so:
{
name: 'my-second-layer',
type: 'geojson',
listens: [
{
listensTo: 'my-first-layer',
type: 'click',
handler: function (feature, map) {
map.hideGeojsonLayer('my-second-layer')
}
}
],
//...
}
the registered handler function gets passed the feature that the user clicked on as the first parameter and the map object as the second
map
Defines details about the map such as tilelayers
assets
Templates for popups are defined as string properties in the config object. You can also optionally define a templateFunction property that accepts a template string and data object. If the templateFunction property is not present Handlebars will be used as the templating engine
CSS for your templates must be included separately.
More information
See dist/example-config.js for existing features
External API
Currently this API is fluid and should be considered unstable, though we will attempt to adhere to semver guidelines during ongoing development
When creating an instance of Mappy from config, the return value is an object with a defined interface
Current API:
showGeojsonLayer('layerName')
hideGeojsonLayer('layerName')
stopDataService('dataServiceName')
startDataService('dataServiceName')
pushToDataService('dataSourceName', geojsonData)
Example:
// Will create map and load data from defined data sources
var map = window.Mappy.create(config);
map.hideGeojsonLayer('layerName') // assuming there was a layer called 'layerName' defined in config
// Example of how to add data manually using a local data source (or can overwrite data for any data source type)
$.getJSON(url).done(function (geojson) {
pushToDataService('local-example', geojson)
})
Development
To work on the project:
- clone the repo
npm install
- run
npm test
to start the test runner - In another terminal run
npm run start:dev
to serve the dist dir, bundle files and watch for file changes. Visitlocalhost:3000
to see the map
Ongoing development notes
Client
- store user preferences (basemap/layers on)
- make use of config as much as possible
- possiblity to have many maps generated from different geojson based data sets
- backend should be able to push updates
- default layer, on by default layers
Server
- possibility to send updates to the client
- adaptors for feeds to node service which can then diff for changes and send server sent events (sse's) to connected clients
Testing
- Set up automated cross-browser testing
Browser Compatibility
WIP, see 'Ongoing development notes' above Manually tested in:
- IE 9, 10, 11
- Chrome 39
- Firefox 33
- Safari 8
Config details
Map
{
domElementId: 'map',
key: {
domElementId: 'key',
title: 'My map key'
},
tileLayers: {
base-tiles: {
url: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '',
maxZoom: 18,
time: {
on: '8:00',
off: '11:00'
},
//any other leaflet tile layer options eg.
zIndex: 100,
opacity: 0.5
}
},
bounds: [
[-43.577988,172.515934],
[-43.461397,172.749529]
],
mapOptions: {
maxZoom: 19
}
}
Layers
[
{
name: 'Major Roadworks',
description: 'This is layer 1',
styles: {
popup: {
css: 'myPopupCssFile.css',
template: 'myTemplate.html'
},
layer: {
general: { // Roughly following leaflet dsl
stroke: true,
weight: 5
},
properties: {
weighting: {
2: {
stroke: true
},
5: {
stroke: false
}
},
lane: {
left: {
colour: 'green'
},
*: {
colour: 'black'
}
}
}
},
icon: {
general: {
iconUrl: '/icons/leaf-green.png',
shadowUrl: '/icons/leaf-shadow.png',
iconSize: [38, 95],
shadowSize: [50, 64],
iconAnchor: [22, 94],
shadowAnchor: [4, 62],
popupAnchor: [-3, -76]
},
properties: {
highway: {
traffic_signals: {
iconUrl: '/icons/leaf-red.png'
},
crossing: {
iconUrl: '/icons/leaf-orange.png'
}
}
},
"cluster": {
"showCoverageOnHover": false
}
}
},
sortOrder: 1, // used to order layers, higher numbers brought to front
startVisible: true // whether to display the layer on map load
defaultLayerType: false || 'default', // false would mean this layer is off if the zoom level is outside
// the levels defined in the 'zoom' config
// 'default', 'heatmap', 'cluster', etc
zoom: [
{
min: 4,
max: 8,
type: 'heatmap'
},
{
min: 9,
max: 16,
type: 'cluster'
}
],
dataSource: 'name-of-defined-dataSource'
}
]
Icon config for layer styles
The keys in 'general' and 'properties' basically follow the leaflet dsl for the Icon class, allowing you to change the icon and its positioning as required.
In the 'general' section if you specify a type: 'divIcon'
then the DivIcon class is used instead, allowing you to specify the html for the icon. This template will be compiled via Handlebars, so you can interpolate properties from the feature that the marker icon is related to, similar to popups.
NOTE: DivIcon does not have a iconUrl
option, so you'll need to add any images using CSS (likely background: url...)
Key
You can either use the default, or define custom html to render the layer key template. Custom templates are parsed through the handlebars templating engine, refer to the handlebar docs for further details.
// default
{
domElementId: 'key', // The dom element used to render the key.
title: 'My map key',
layers: [
{
name: 'layer1',
description: 'My Layer',
checked: true
}
]
}
// custom template
{
domElementId: 'key', // The dom element used to render the key.
title: 'My map key',
template: '<ul class="items"><ul/>', // Handlebars template used to render the key wrapper.
itemTemplate: '<li><label for="{{key}}">{{#if text}}<span>{{text}}</span>{{/if}}<input type="checkbox" id="{{key}}" {{#if checked}}checked="checked"{{/if}}></label></li>', // Handlebars template used to render the key items.
listWrapperClass: '.items', // Dom element in 'template' key wrapper used to render the key items.
layers: [
{
name: 'layer1',
description: 'My Layer',
checked: true,
templateData: { // optional data to pass through to the 'itemTemplate'
example1: 'test' // can be referenced thrigh the 'itemTemplate' with 'templateData.example1'
}
}
]
}
Clustering
Enable clustering on a layer by setting the "cluster" property on layer icon config to 'true', alternatively you can pass through options to override the default cluster settings. You can refer to the Leaflet.markercluster module documentation for configuration options.
Possible values
To enable clustering
cluster: true //default options
cluster: { ... } //overide default options
The following will disable clustering
cluster: false
cluster: null
cluster: undefined
If you specify an icon it will override iconCreateFunction
to create the icon with the given config. If showClusterCount
is also truthy then it will render div's instead, with the cluster count inside. See the example below, with css to show an image AND cluster count.
Note: If you want to use clustering with the default styling you will need to include the default marker clusterer stylesheet. eg.
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/0.4.0/MarkerCluster.Default.css" />
Clustering example
{
layers: [
{
name: 'layer1',
...
styles: {
icon: {
cluster: {
showCoverageOnHover: false,
icon: {
showClusterCount: true, // if false, specify normal icon options e.g. iconUrl
iconClass: 'leafy-background',
iconSize: [38, 95],
iconAnchor: [22, 94],
popupAnchor: [-3, -76]
}
}
}
}
}
]
}
with CSS for the cluster count icon
.leaflet-div-icon {
background-color: transparent;
border: none;
}
.leafy-background {
background-image: url(/icons/leaf-green.png);
width: 100%;
height: 100%;
}
.leafy-background .cluster-count {
text-align: center;
padding-top: 15px;
}
To generate a Google tile base layer specify the 'google' type on the map property tileLayers config.
Include the Google javascript api to your project html <script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script>
.
You can also specify a 'mapType' to display (see the Google API for different map types). This will default to ROADMAP.
var config = {
map: {
domElementId: 'map',
tileLayers: {
base-tiles: {
type: 'google',
maxZoom: 18,
zIndex: 10,
mapType: 'SATELLITE'
}
},
bounds: [
[-43.577988,172.515934],
[-43.461397,172.749529]
]
}
}