npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mappy

v1.0.0

Published

A configurable map engine

Downloads

40

Readme

MAPPY

Gitter

WARNING: This library is under heavy development and should be considered ALPHA We don't recommend using in production at this time.

Build Status

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 and dist/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 and dist/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 interval
  • singlePoll to only load the data from a URL once
  • local to not load any data automatically. Layer data can be added via map.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. Visit localhost: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;
}

Google

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]
    ]
  }
}