Bicycle Master Plan is a tool / web app for displaying various bicycle infrastructure related data on a map. Actually, you can use any OpenStreetMap / custom data on a map, not just bicycle-related data. Extensive visual customization is possible by automatical conversion of OSM keys and values to CSS classes.
It supports the following sources of data:
- OSM data (JSON format; tiles, paths/ways and markers)
- database data (seed your database; markers)
- GPS EXIF-tagged photos (automatic using default seeder; markers)
- custom feed data (from any public URL/API in JSON format; markers)
- custom data (from rows in Google Sheets; markers only)
- data provided by users via form (if editable layer enabled; markers)
Built with:
- Laravel (PHP)
- Leaflet (JS)
https://mapa.cyklokoalicia.sk/bratislava/public/
- download the code (git clone or ZIP file)
- run
composer installto download dependencies - run
php artisan migrateto setup database - check/set
public/.htaccessfile permissions, if necessary - check/set
storage/app/permissions, if necessary (755 for writing) - create
storage/app/public/uploads/directory (755), if an editable layer is enabled - create
storage/app/osm/directory (755), if OSM layer download is enabled (see below) - symlink
public/storage/(TARGET) tostorage/app/public/uploads/(SOURCE), if an editable layer is enabled (see https://laravel.com/docs/7.x/filesystem#the-public-disk)
- Edit
config/map.php- Set basic info such as map name, language, bounding box, center and zoom
- Configure
layers:- a base layer (usually a background map tile layer) is always
0in config file type=path,marker,combinedname= name of a layer (can contain HTML tags)class= CSS class to be used to mark up layer items (markers / paths)icon= layer item icon (markers only) will be created from eithernameorfilenamein database- optional
file= OSM JSON file containing layer content (markers or paths/ways downloaded from OSM) - optional
cluster=truefor layers to group/cluster items/markers - optional
editable=truefor the user editable layer (user submitted items require admin approval, see below) - optional
types= array a layer can contain multiple types of items such as different sets of markers etc.
- a base layer (usually a background map tile layer) is always
- optional Configure OSM data/layers to download:
osm_server=https://lz4.overpass-api.de/api/interpreter(use any OSM server)osm_data= array of map layers withfileparameter:file= filename to save the filedata= overpass query to download OSM data, e.g.[out:json]; (relation[network=lcn]({{bbox}}); ); out body; >; out skel qt;, see https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_API_by_Example
- optional Configure admin emails (see below on how to enable admin functionality):
admins= array of admin emails, e.g. ['[email protected]', '[email protected]']
- optional If you enable an editable layer, user submitted items require admin approval to be displayed. Set
approvedcolumn to1in database. If user marked an item asoutdated(1in database), setdeletedto1in database to hide it from a map. - optional If you want to display bikeshare data on your map, see file
config/bikeshare.example.phpfor details on how to enable data download (public bikeshare API endpoint required) - optional If you want to display additional custom data/markers on your map from Google Sheets, see
config/google.example.phpfile on how to configure it (you will need to create a project with service access withclient_idand JSONkeyfileat https://console.developers.google.com) - optional If you require admin functionality, you will need to obtain client ID + client secret from OAuth2 credentials at https://console.developers.google.com. Once you have these, edit
config/services.phpand add the following lines:
'google' => [
'client_id' => 'your_client_id',
'client_secret' => 'your_client_secret',
'redirect' => 'callback URL', // callback URL for OAuth authentication, e.g. http://example.com/login/google/callback
],
All standard map tiles providers are supported.
Open public/css/main.css to customize layer markers or styles of paths etc. SVG properties (fill, stroke etc.) need to be used for styling paths/OSM ways, see https://css-tricks.com/svg-properties-and-css/.
Example of path classes created from OpenStreetMap data (bicycle lane):
class="path cycleway-left-lane cycleway-right-shared_lane foot-use_sidepath highway-residential lit-yes maxspeed-30 name-dunajska name-hu-dunautca surface-asphalt trolley_wire-yes"
Example of marker classes created from OpenStreetMap data (bicycle parking):
class="marker access-private amenity-bicycle_parking covered-yes surveillance-yes parking"
Any combinations of keys / values can be easily styled for your purposes by using standardized CSS.
Setup cron to call refresh URLs daily (or other interval), e.g.:
15 0 * * * /usr/bin/curl --silent https://example.com/public/refresh/osm >/dev/null 2>&1
Update endpoints are:
- OSM data:
/refresh/osm - Bikeshare feed data:
/refresh/bikeshare - Google sheets data:
/refresh/googlesheet - Feed data:
/refresh/feed
- Upload photos/files to
public/photosdirectory (create this directory, if it does not exist) - run
php artisan db:seed --class=PhotosSeeder - Seeder will process all photos in the directory and extract their GPS coordinates from EXIF tags and add them to database as markers
- Enable photo layer by editing
config/map.php(enabling clustering will help with large number of photos) and adding this code tolayers:
1 => [
'type' => 'marker',
'name' => 'Your photos',
'class' => 'photo',
'icon' => 'filename',
'cluster' => true,
'options' => [
'disableClusteringAtZoom' => 17,
],
],
- Refresh your map to see your photos
- Edit
config/map.phpand add a layer (change number5to suit your purposes) to thelayers:
5 => [
'type' => 'path',
'name' => 'Cycling paths<br><span class="cycleway-lane">━━━</span> Segregated<br><span class="cycleway-shared_lane">━━━</span> Shared<br><span class="lcn-provisional">• • • •</span> Recommended<br><span class="highway-pedestrian">━━━</span> Pedestrianized<br><span class="mtb-scale">━━━</span> For mountain bikes',
'class' => 'ways',
'file' => 'ways.json',
],
- Add OSM instructions for fetching data in
config/map.php. Changenetworkoperator name to your city's one (e.g.Slovnaft BAjkfor Bratislava):
// OSM data to fetch
'osm_server' => 'https://lz4.overpass-api.de/api/interpreter',
'osm_data' => [
[
'file' => 'ways.json',
'data' => '[out:json]; (way[cycleway]({{bbox}}); way["cycleway:left"]({{bbox}}); way["cycleway:right"]({{bbox}}); way[highway=pedestrian]({{bbox}}); way[highway=cycleway]({{bbox}}); way[bicycle=yes]({{bbox}}); way[bicycle=official]({{bbox}}); way[lcn]({{bbox}}); way[bicycle=designated]({{bbox}}); ); out body; >; out skel qt;',
],
[
'file' => 'bikeshare-sb.json',
'data' => '[out:json]; (node[network="Slovnaft BAjk"]({{bbox}}); ); out body; >; out skel qt;',
],
],
- Edit
config/map.phpand add a layer (change number2to suit your purposes) to thelayers:
2 => [
'type' => 'marker',
'name' => 'Bicycle parking<br><span class="parking"></span> <span class="parking bicycle_parking-rack"></span> <span class="parking bicycle_parking-shed"></span> Safe<br><span class="parking bicycle_parking-anchors"></span> Unsuitable<br><span class="amenity-bicycle_repair_station"></span> Public pump and tools',
'class' => 'parking',
'file' => 'parking.json',
],
- Add OSM instructions for fetching data in
config/map.php:
'osm_server' => 'https://lz4.overpass-api.de/api/interpreter',
'osm_data' => [
[
'file' => 'parking.json',
'data' => '[out:json]; (node[amenity="bicycle_parking"]({{bbox}}); node["amenity"="bicycle_repair_station"]({{bbox}}); ); out body; >; out skel qt;',
],
],