@binary-constructions/semantic-map
Version: 
A markup wrapper for the Leaflet map API
294 lines (219 loc) • 12.9 kB
Markdown
# semantic-map
A wrapper for [Leaflet](https://leafletjs.com/) allowing you to
display maps on your website by writing HTML markup only.
**Warning:** *this project is currently in a relatively early state of
development with only the most basic functionality implemented and a
lot of polishing still to be done.  The documentation below is also a
little bit out-of-date.  I'm busy with other things at the moment but
should be able to get back to this project at some point in the not
too distant future.  Should you be interested in using semantic-map
please make sure to contact me first!*
## Example
A very simple map might look as follows:
```html
<div class='semantic-map'
    data-semantic-map-tile-url-template='https://api.tiles.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=YOUR_ACCESS_TOKEN'
    data-semantic-map-initial-center='[13.4, 52.52]'
    data-semantic-map-initial-zoom='12'>
  <div class='semantic-map__feature'>
    <div class='semantic-map__geometry' data-semantic-map-geometry-type='Point' data-semantic-map-geometry-coordinates='[13.4, 52.52]'>
      <div class='semantic-map__property' data-semantic-map-property-key='popup'>
        <h4>Hey, I'm a Point!</h4>
        <p>I mark the initial center of the map.</p>
      </div>
    </div>
  </div>
  <div class='semantic-map__attribution'>
    Map data © <a href='https://www.openstreetmap.org/'>OpenStreetMap</a> contributors, <a href='https://creativecommons.org/licenses/by-sa/2.0/'>CC-BY-SA</a>, Imagery © <a href='https://www.mapbox.com/'>Mapbox</a>
  </div>
</div>
```
For more examples see the file `showcase.html` in the `dist/` folder.
## Features
- Display one or more maps.  (*Yay*!)
- Add map features like points and polygons.
- Define content to show in a popup when a map feature is selected.
- Customize basic map behavior like min/max zoom, clustering,
  etc. (**TODO:** map bounds, automatic panning/zooming when the
  visible features change, initial center/zoom defined by the map
  features present, initial center/zoom defined by a bounding box,
  ...).
- Define map features not only as part of the map itself but anywhere
  in your markup (e.g. together with a list of addresses that you are
  showing elsewhere on your page anyway).
- Add any kind of additional properties to a feature (with some of
  those properties having customizable default effects, like `popup`
  above).
- **TODO:** group features into (possibly nested) collections with
  shared properties.
- **TODO:** add callbacks for map feature selection, etc.
- **TODO:** define the markup of popups (amongst other things) using
  lodash templates.
- **TODO:** add custom hooks to transform property values, style map
  features, etc.
## Limitations
The main focus of this project is to let you easily add *basic* maps
to a webpage by dumping all of the relevant data directly into your
markup.  While more functionality might be added in the future in ways
that do not conflict with the overall design goals, it is *not* the
aim of this project to make the whole of the backend APIs accessible
via markup.
## Installation
### As a `<script>`
First load the script into your webpage:
```html
<script src="path/to/semantic-map.min.js"></script>
```
Then add another script element to mount all maps when the page is
loaded:
```html
<script>
  window.onload = function() {
    semanticMap.mount();
  }
</script>
```
You probably also want to extend your style definitions with simple
CSS rules similar to the following:
```css
.semantic-map {
    height: 400px; /* ... or whatever the height of your map(s) is supposed to be. */
}
.semantic-map__attribution,
.semantic-map__property:not([data-semantic-map-property-value]) {
    display: none;
}
```
### Via npm/yarn
The npm package of semantic-map is not currently being kept up-to-date
so for now it shouldn't be used!
## Usage
### A Note on Coordinates
Like GeoJSON -- and unlike Leaflet amongst others -- **semantic-map**
uses the order `[LON, LAT]` to define coordinates.
### Merging of Map Elements
To allow for terser markup **semantic-map** allows the "merging" of
basically any kind of nested elements.  Because of that, the map
feature in the example above could also be written as:
```html
  <div class='semantic-map__feature semantic-map__geometry'
       data-semantic-map-geometry-type='Point'
       data-semantic-map-geometry-coordinates='[13.4, 52.52]'>
    <div class='semantic-map__property' data-semantic-map-property-key='popup'>
      <h4>Hey, I'm a Point!</h4>
      <p>I mark the initial center of the map.</p>
    </div>
  </div>
```
Or even:
```html
  <div class='semantic-map__feature semantic-map__geometry semantic-map__property'
       data-semantic-map-geometry-type='Point'
       data-semantic-map-geometry-coordinates='[13.4, 52.52]'
       data-semantic-map-property-key='popup'>
    <h4>Hey, I'm a Point!</h4>
    <p>I mark the initial center of the map.</p>
  </div>
```
While there are certain cases where the specific semantics of merged
elements might not be completely obvious (e.g.: merging `semantic-map`
with `semantic-map-context`), in most *sensible* cases the results
should be what you expect.
### The Markup API
The **Markup API** is designed to be straigtforward to use while still
being adaptable to different requirements with respect to the
structuring of a webpage.
#### `.semantic-map`
Defines the mount point for a map.  Several such mount points may exist
in a given webpage.
| Attribute                             | Value                                                                                             | Required |
|---------------------------------------|:-------------------------------------------------------------------------------------------------:|:--------:|
| `data-semantic-map-id`                | an identifier for this map to reference from a `.semantic-map-context`                            | no       |
| `data-semantic-map-tile-url-template` | the URL from where to load the map data (which should already include the access token if needed) | yes      |
| `data-semantic-map-initial-center`    |                                                                                                   | no       |
| `data-semantic-map-initial-zoom`      |                                                                                                   | no       |
| `data-semantic-map-fit-bounds`        |                                                                                                   | no       |
| `data-semantic-map-max-zoom`          |                                                                                                   | no       |
| `data-semantic-map-min-zoom`          |                                                                                                   | no       |
| `data-semantic-map-enable-clustering` |                                                                                                   | no       |
**Notes:**
- Additional map details (e.g. features like points or polygons) may be
  added to a map by making the corresponding elements children of the
  map element.  Any element placed in such a way will automatically have
  the parent element as the target map.
- If map details need to be defined outside of the map element itself,
  they may also be placed inside a `.semantic-map-context` element
  instead (see below).
#### `.semantic-map-context`
Allows the placement of map details outside of the actual map elements
by treating both the context element itself as well as all of its
childred as if they were children of the corresponding map.
| Attribute               | Value                                        | Required                                           |
|-------------------------|:--------------------------------------------:|:--------------------------------------------------:|
| `data-semantic-map-ref` | the `data-semantic-map-id` of the target map | no (defaults to the first map without a custom id) |
#### `.semantic-map__feature`
Defines an individual map feature via a child element.
**Notes:**
- Every such element needs to contain *exactly* one child element of
  class `.semantic-map__geometry` (making these two elements a prime
  candidate for merging; see above.)
- Additionally, features *may* contain properties.
#### `.semantic-map__features`
Defines one or more map features using JSON.
| Attribute                    | Value                              | Required |
|------------------------------|------------------------------------|----------|
| `data-semantic-map-features` | JSON defining one or more features | yes      |
**Notes:**
- If the provided JSON defines an array, it must consist entirely of
  valid **Feature Objects** as defined by the [GeoJSON
  spec](https://tools.ietf.org/html/rfc7946#section-3.2) (i.e. each
  must have a member "type" with a value of "Feature").
- If the provided JSON defines an object instead, it may be any kind
  of valid **GeoJSON Object** as per the spec linked above (i.e. it
  must have a member "type" with a value of either "Feature",
  "FeatureCollection" or one of the seven GeoJSON geometry types).
**TODO:** clarify/document the semantics of adding properties to this
element.
#### `.semantic-map__geometry`
Defines an individual geometry.
| Attribute                                | Value                                                                                                            | Required                                     |
|------------------------------------------|------------------------------------------------------------------------------------------------------------------|----------------------------------------------|
| `data-semantic-map-geometry-type`        | one of "Point", "LineString", "Polygon", "MultiPoint", "MultiLineString", "MultiPolygon" or "GeometryCollection" | yes                                          |
| `data-semantic-map-geometry-coordinates` | the coordintes of the geometry in question                                                                       | yes, unless the type is a GeometryCollection |
**Notes:**
- As the only valid case of nested geometries a "GeometryCollection" is
  allowed to have multiple other geometry elements to be nested inside
  it (as long as they are not themselves of type "GeometryCollection").
**Note:** properties may not be defined for geometries but need to be
defined for the corresponding feature instead.
#### `.semantic-map__geometries`
Defines a list of geometries via attributes.
| Attribute                      | Value                              | Required |
|--------------------------------|------------------------------------|----------|
| `data-semantic-map-geometries` | a list of GeoJSON geometry objects | yes      |
**Notes:**
- This element is only meant to be used inside a geometry of type
  "GeometryCollection".
#### `.semantic-map__property`
Defines a single property of a feature.
| Attribute                          | Value                                                                                                    | Required                   |
|------------------------------------|:--------------------------------------------------------------------------------------------------------:|:--------------------------:|
| `data-semantic-map-property-name`  | the name of the property                                                                                 | yes                        |
| `data-semantic-map-property-value` | the value of the property                                                                                | no                         |
| `data-semantic-map-property-parse` | whether to parse the value attribute literally ("literal"), HTML-escaped ("escaped") or as JSON ("json") | no (defaults to "literal") |
**Notes:**
- If the `data-semantic-map-property-value` attribute is not provided
  the value of the property becomes the `.innerHTML` of the property
  element.
- The different parse types only have an effect if the property value
  is specified via an attribute.
**TODO:** once feature collections are implemented they'll also be
allowed to have properties (to use as default properties for features,
mainly).  The specific semantics of cascading properties (e.g.:
overwriting vs. merging) still have to be worked out, however.
#### `.semantic-map__properties`
Defines multiple properties of a feature via JSON.
| Attribute                      | Value                                 | Required |
|--------------------------------|:-------------------------------------:|:--------:|
| `data-semantic-map-properties` | a JSON object defining the properties | yes      |
**TODO:** clarify/document the semantics of nesting properties