Create your own tile server and map client

  1. Get the data for maps
  2. Process and save data into Postgres database
  3. Create tiles from Postgres
  4. Create a client that makes specific requests to get tile data
  1. Start the Postgres server: pg_ctl -D /usr/local/var/postgres -l /usr/local/var/postgres/server.log start
  2. Create a database called gis, with spatial extensions: psql -d gis -c ‘CREATE EXTENSION hstore; CREATE EXTENSION postgis;’
osm2pgsql -d gis ~/path/to/data.osm.pbf — style
./osm2pgsql -U postgres -H localhost -d gis -W “.\california-latest.osm.pbf” — style — slim — number-processes 8
pip install python-mapnik # thanks to Hüseyin Çapan for reporting it./ osm.xml — dbname gis — host localhost — user postgres — accept-none > out.xml
if __name__ == "__main__":
home = os.environ['HOME']
mapfile = os.environ['MAPNIK_MAP_FILE']
except KeyError:
mapfile = home + "/"
tile_dir = os.environ['MAPNIK_TILE_DIR']
except KeyError:
tile_dir = home + "/osm/tiles/"
if not tile_dir.endswith('/'):
tile_dir = tile_dir + '/'
# Change the following for different bounding boxes and zoom levels
minZoom = 10
maxZoom = 16
bbox = (-2, 50.0,1.0,52.0)
render_tiles(bbox, mapfile, tile_dir, minZoom, maxZoom)
  1. Create an environment variable called MAPNIK_MAP_FILE with the path to out.xml we generated in the previous step
  2. Create an environment variable called MAPNIK_TILE_DIR with a path to where you want the tiles to be saved
  3. Start generating tiles — Run python, in a terminal / command prompt (in the directory where this file exists)
import os.path
from flask import Flask, send_file
app = Flask(__name__, static_url_path='/static')@app.route('/tiles/<zoom>/<y>/<x>', methods=['GET', 'POST'])
def tiles(zoom, y, x):
default = '_path_to_default_tile\\tiles\\0\\11\\333\\831.png' # this is a blank tile, change to whatever you want
filename = '_path_to_tiles\\tiles\\0\\%s\\%s\\%s.png' % (zoom, x, y)
if os.path.isfile(filename):
return send_file(filename)
return send_file(default)
@app.route('/', methods=['GET', 'POST'])
def index():
return app.send_static_file('index.html')
if __name__ == '__main__':, host='localhost', port=8080)
<title>My Maps</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no'/>
<script type="text/javascript" src='leaflet.js'></script>
<link rel="stylesheet" type="text/css" href="leaflet.css">
<div id='map'></div>
<script type="text/javascript">
var map ='map', {
center: [40, -110], // change to center at the region you generated the tiles for
zoom: 5,
subdomains: []
L.tileLayer('http://localhost:8080/tiles/{z}/{y}/{x}', {
maxZoom: 18,
attribution: '(C) 2016 Nitin Pasumarthy'




Applied Deep Learning Engineer | LinkedIn

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Beware of Differences!

Waila Harvestability Mod 1.16.3–1.15.2 For Minecraft — Block display —

The set and get methods from lodash

5 Best Shared Hosting Providers In 2022

Download In ^&PDF Modern PyQt: Create GUI Applications for Project Management, Computer Vision, and…

Date format

Sending Emails using Flutter

Decoding Documentation — PayPal Integration in Unity

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nitin Pasumarthy

Nitin Pasumarthy

Applied Deep Learning Engineer | LinkedIn

More from Medium

Translator with Azure and Docker

Docker Storage Drivers

Self Hosted Docker Agents in Azure

Easily Create VMs for Docker Swarm Development