From 8e9e103e9f748fe7a104c4fba00cc72a847e2d3a Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Mon, 17 Jun 2013 23:08:37 +0100 Subject: [PATCH] Add tile cookbook --- cookbooks/tile/README.rdoc | 8 + cookbooks/tile/attributes/default.rb | 3 + cookbooks/tile/files/default/html/abuse.png | Bin 0 -> 21004 bytes cookbooks/tile/files/default/html/abuse2.png | Bin 0 -> 116 bytes cookbooks/tile/files/default/html/abuse3.png | Bin 0 -> 197245 bytes .../files/default/html/clientaccesspolicy.xml | 13 + cookbooks/tile/files/default/html/favicon.ico | Bin 0 -> 1150 bytes .../files/default/html/update-url-tile.png | Bin 0 -> 3713 bytes cookbooks/tile/files/default/ruby/expire.rb | 163 ++++++++ cookbooks/tile/metadata.rb | 10 + cookbooks/tile/recipes/default.rb | 385 ++++++++++++++++++ cookbooks/tile/templates/default/apache.erb | 46 +++ .../tile/templates/default/expire-tiles.erb | 9 + cookbooks/tile/templates/default/export.erb | 150 +++++++ .../templates/default/logrotate.apache.erb | 16 + cookbooks/tile/templates/default/munin.erb | 4 + .../tile/templates/default/renderd.conf.erb | 19 + .../default/replicate.configuration.erb | 8 + .../tile/templates/default/replicate.erb | 77 ++++ .../tile/templates/default/replicate.init.erb | 24 ++ .../templates/default/replicate.logrotate.erb | 10 + .../tile/templates/default/tile.conf.erb | 33 ++ 22 files changed, 978 insertions(+) create mode 100644 cookbooks/tile/README.rdoc create mode 100644 cookbooks/tile/attributes/default.rb create mode 100644 cookbooks/tile/files/default/html/abuse.png create mode 100644 cookbooks/tile/files/default/html/abuse2.png create mode 100644 cookbooks/tile/files/default/html/abuse3.png create mode 100644 cookbooks/tile/files/default/html/clientaccesspolicy.xml create mode 100644 cookbooks/tile/files/default/html/favicon.ico create mode 100644 cookbooks/tile/files/default/html/update-url-tile.png create mode 100755 cookbooks/tile/files/default/ruby/expire.rb create mode 100644 cookbooks/tile/metadata.rb create mode 100644 cookbooks/tile/recipes/default.rb create mode 100644 cookbooks/tile/templates/default/apache.erb create mode 100644 cookbooks/tile/templates/default/expire-tiles.erb create mode 100755 cookbooks/tile/templates/default/export.erb create mode 100644 cookbooks/tile/templates/default/logrotate.apache.erb create mode 100644 cookbooks/tile/templates/default/munin.erb create mode 100644 cookbooks/tile/templates/default/renderd.conf.erb create mode 100644 cookbooks/tile/templates/default/replicate.configuration.erb create mode 100644 cookbooks/tile/templates/default/replicate.erb create mode 100644 cookbooks/tile/templates/default/replicate.init.erb create mode 100644 cookbooks/tile/templates/default/replicate.logrotate.erb create mode 100644 cookbooks/tile/templates/default/tile.conf.erb diff --git a/cookbooks/tile/README.rdoc b/cookbooks/tile/README.rdoc new file mode 100644 index 000000000..3de2ec7a3 --- /dev/null +++ b/cookbooks/tile/README.rdoc @@ -0,0 +1,8 @@ += DESCRIPTION: + += REQUIREMENTS: + += ATTRIBUTES: + += USAGE: + diff --git a/cookbooks/tile/attributes/default.rb b/cookbooks/tile/attributes/default.rb new file mode 100644 index 000000000..79d55d10a --- /dev/null +++ b/cookbooks/tile/attributes/default.rb @@ -0,0 +1,3 @@ +default[:tile][:data] = {} +default[:tile][:styles] = {} +default[:tile][:tile_directory] = "/srv/tile.openstreetmap.org/tiles" diff --git a/cookbooks/tile/files/default/html/abuse.png b/cookbooks/tile/files/default/html/abuse.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f7910d35d31292cfc0c7534328879c4dadee9a GIT binary patch literal 21004 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 QZU6uP07*qoM6N<$f_sca=l}o! literal 0 HcmV?d00001 diff --git a/cookbooks/tile/files/default/html/abuse2.png b/cookbooks/tile/files/default/html/abuse2.png new file mode 100644 index 0000000000000000000000000000000000000000..ba40a46a0987b20582cf0fff2927ddbd518ebbb4 GIT binary patch literal 116 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 + + + + + + + + + + + + diff --git a/cookbooks/tile/files/default/html/favicon.ico b/cookbooks/tile/files/default/html/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..27b042b5cec16967462ab011cf4b47320fbc14c4 GIT binary patch literal 1150 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 literal 0 HcmV?d00001 diff --git a/cookbooks/tile/files/default/ruby/expire.rb b/cookbooks/tile/files/default/ruby/expire.rb new file mode 100755 index 000000000..1c2993b71 --- /dev/null +++ b/cookbooks/tile/files/default/ruby/expire.rb @@ -0,0 +1,163 @@ +#!/usr/bin/ruby + +require 'rubygems' +require 'proj4' +require 'xml/libxml' +require 'set' +require 'pg' +require 'time' + +module Expire + # projection object to go from latlon -> spherical mercator + PROJ = Proj4::Projection.new(["+proj=merc", "+a=6378137", "+b=6378137", + "+lat_ts=0.0", "+lon_0=0.0", "+x_0=0.0", + "+y_0=0", "+k=1.0", "+units=m", + "+nadgrids=@null", "+no_defs +over"]) + + # width/height of the spherical mercator projection + SIZE=40075016.6855784 + # the size of the meta tile blocks + METATILE = 8 + # the directory root for meta tiles + HASH_ROOT = "/tiles/default/" + # lowest zoom that we want to expire + # MIN_ZOOM=12 + MIN_ZOOM=13 + # highest zoom that we want to expire + MAX_ZOOM=18 + # database parameters + DBNAME="gis" + DBHOST="" + #DBPORT=5432 + DBPORT=5432 + DBTABLE="planet_osm_nodes" + + # turns a spherical mercator coord into a tile coord + def Expire.tile_from_merc(point, zoom) + # renormalise into unit space [0,1] + point.x = 0.5 + point.x / SIZE + point.y = 0.5 - point.y / SIZE + # transform into tile space + point.x = point.x * 2 ** zoom + point.y = point.y * 2 ** zoom + # chop of the fractional parts + [point.x.to_int, point.y.to_int, zoom] + end + + # turns a latlon -> tile x,y given a zoom level + def Expire.tile_from_latlon(latlon, zoom) + # first convert to spherical mercator + point = PROJ.forward(latlon) + tile_from_merc(point, zoom) + end + + # this must match the definition of xyz_to_meta in mod_tile + def Expire.xyz_to_meta(root, x, y, z) + # mask off the final few bits + x &= ~(METATILE - 1) + y &= ~(METATILE - 1) + # generate the path + hash_path = (0..4).collect { |i| + (((x >> 4*i) & 0xf) << 4) | ((y >> 4*i) & 0xf) + }.reverse.join('/') + root + '/' + z.to_s + '/' + hash_path + ".meta" + end + + # time to reset to, some very stupidly early time, before OSM started + EXPIRY_TIME = Time.parse("2000-01-01 00:00:00") + + # expire the meta tile by setting the modified time back + def Expire.expire_meta(meta) + puts "Expiring #{meta}" + File.utime(EXPIRY_TIME, EXPIRY_TIME, meta) + end + + def Expire.expire(change_file) + do_expire(change_file) do |set| + new_set = Set.new + meta_set = Set.new + + # turn all the tiles into expires, putting them in the set + # so that we don't expire things multiple times + set.each do |xy| + # this has to match the routine in mod_tile + meta = xyz_to_meta(HASH_ROOT, xy[0], xy[1], xy[2]) + + meta_set.add(meta) if File.exist? meta + + # add the parent into the set for the next round + new_set.add([xy[0] / 2, xy[1] / 2, xy[2] - 1]) + end + + # expire all meta tiles + meta_set.each do |meta| + expire_meta(meta) + end + + # return the new set, consisting of all the parents + new_set + end + end + + def Expire.do_expire(change_file, &block) + # read in the osm change file + doc = XML::Document.file(change_file) + + # hash map to contain all the nodes + nodes = Hash.new + + # we put all the nodes into the hash, as it doesn't matter whether the node was + # added, deleted or modified - the tile will need updating anyway. + doc.find('//node').each do |node| + lat = node['lat'].to_f + if lat < -85 + lat = -85 + end + if lat > 85 + lat = 85 + end + point = Proj4::Point.new(Math::PI * node['lon'].to_f / 180, + Math::PI * lat / 180) + nodes[node['id'].to_i] = tile_from_latlon(point, MAX_ZOOM) + end + + # now we look for all the ways that have changed and put all of their nodes into + # the hash too. this will add too many nodes, as it is possible a long way will be + # changed at only a portion of its length. however, due to the non-local way that + # mapnik does text placement, it may stil not be enough. + # + # also, we miss cases where nodes are deleted from ways where that node is not + # itself deleted and the coverage of the point set isn't enough to encompass the + # change. + conn = PG::Connection.new(:host => DBHOST, :port => DBPORT, :dbname => DBNAME) + doc.find('//way/nd').each do |node| + node_id = node['ref'].to_i + unless nodes.include? node_id + # this is a node referenced but not added, modified or deleted, so it should + # still be in the postgis DB. + res = conn.query("select lon, lat from #{DBTABLE} where id=#{node_id};") + + # loop over results, adding tiles to the change set + res.each do |row| + point = Proj4::Point.new(row[0].to_f / 100.0, row[1].to_f / 100.0) + nodes[node_id] = tile_from_merc(point, MAX_ZOOM) + end + + # Discard results + res.clear + end + end + + # create a set of all the tiles at the maximum zoom level which are touched by + # any of the nodes we've collected. we'll create the tiles at other zoom levels + # by a simple recursion. + set = Set.new nodes.values + + # expire tiles and shrink to the set of parents + (MAX_ZOOM).downto(MIN_ZOOM) do |z| + # allow the block to work on the set, returning the set at the next + # zoom level + set = yield set + end + end +end diff --git a/cookbooks/tile/metadata.rb b/cookbooks/tile/metadata.rb new file mode 100644 index 000000000..6d261cf0d --- /dev/null +++ b/cookbooks/tile/metadata.rb @@ -0,0 +1,10 @@ +maintainer "OpenStreetMap Administrators" +maintainer_email "admins@openstreetmap.org" +license "Apache 2.0" +description "Installs and configures tile servers" +long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc')) +version "1.0.0" +depends "apache" +depends "git" +depends "nodejs" +depends "postgresql" diff --git a/cookbooks/tile/recipes/default.rb b/cookbooks/tile/recipes/default.rb new file mode 100644 index 000000000..d60d52c2c --- /dev/null +++ b/cookbooks/tile/recipes/default.rb @@ -0,0 +1,385 @@ +# +# Cookbook Name:: tile +# Recipe:: default +# +# Copyright 2013, OpenStreetMap Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include_recipe "apache" +include_recipe "git" +include_recipe "nodejs" +include_recipe "postgresql" + +blocks = data_bag_item("tile", "blocks") + +apache_module "alias" +apache_module "expires" +apache_module "headers" +apache_module "remoteip" +apache_module "rewrite" + +apache_module "tile" do + conf "tile.conf.erb" +end + +tilecaches = search(:node, "roles:tilecache") + +apache_site "default" do + action [ :disable ] +end + +apache_site "tile.openstreetmap.org" do + template "apache.erb" + variables :caches => tilecaches +end + +template "/etc/logrotate.d/apache2" do + source "logrotate.apache.erb" + owner "root" + group "root" + mode 0644 +end + +directory "/srv/tile.openstreetmap.org" do + owner "tile" + group "tile" + mode 0755 +end + +package "renderd" + +service "renderd" do + action [ :enable, :start ] + supports :status => false, :restart => true, :reload => false +end + +directory node[:tile][:tile_directory] do + owner "tile" + group "www-data" + mode 0775 +end + +if node[:tile][:tile_directory] != "/srv/tile.openstreetmap.org/tiles" + link "/srv/tile.openstreetmap.org/tiles" do + to node[:tile][:tile_directory] + end +end + +template "/etc/renderd.conf" do + source "renderd.conf.erb" + owner "root" + group "root" + mode 0644 + notifies :reload, resources(:service => "apache2") + notifies :restart, resources(:service => "renderd") +end + +remote_directory "/srv/tile.openstreetmap.org/html" do + source "html" + owner "tile" + group "tile" + mode 0755 + files_owner "tile" + files_group "tile" + files_mode 0644 +end + +directory "/srv/tile.openstreetmap.org/cgi-bin" do + owner "tile" + group "tile" + mode 0755 +end + +template "/srv/tile.openstreetmap.org/cgi-bin/export" do + source "export.erb" + owner "tile" + group "tile" + mode 0755 + variables :blocks => blocks +end + +directory "/srv/tile.openstreetmap.org/data" do + owner "tile" + group "tile" + mode 0755 +end + +node[:tile][:data].each do |name,data| + url = data[:url] + file = "/srv/tile.openstreetmap.org/data/#{File.basename(url)}" + directory = "/srv/tile.openstreetmap.org/data/#{data[:directory]}" + + directory directory do + owner "tile" + group "tile" + mode 0755 + end + + if file =~ /\.tgz$/ + package "tar" + + execute file do + action :nothing + command "tar -zxf #{file} -C #{directory}" + user "tile" + group "tile" + end + elsif file =~ /\.tar\.bz2$/ + package "tar" + + execute file do + action :nothing + command "tar -jxf #{file} -C #{directory}" + user "tile" + group "tile" + end + elsif file =~ /\.zip$/ + package "unzip" + + execute file do + action :nothing + command "unzip -qq #{file} -d #{directory}" + user "tile" + group "tile" + end + end + + if data[:processed] + original = "#{directory}/#{data[:original]}" + processed = "#{directory}/#{data[:processed]}" + + package "gdal-bin" + + execute processed do + action :nothing + command "ogr2ogr #{processed} #{original}" + user "tile" + group "tile" + subscribes :run, resources(:execute => file), :immediately + end + end + + remote_file file do + action :create_if_missing + source url + owner "tile" + group "tile" + mode 0644 + notifies :run, resources(:execute => file), :immediately + notifies :restart, resources(:service => "renderd") + end +end + +nodejs_package "carto" +nodejs_package "millstone" + +directory "/srv/tile.openstreetmap.org/styles" do + owner "tile" + group "tile" + mode 0755 +end + +node[:tile][:styles].each do |name,details| + directory = "/srv/tile.openstreetmap.org/styles/#{name}" + + git directory do + action :sync + repository details[:repository] + revision details[:revision] + user "tile" + group "tile" + end + + link "#{directory}/data" do + to "/srv/tile.openstreetmap.org/data" + owner "tile" + group "tile" + end + + execute "#{directory}/project.mml" do + command "carto project.mml > project.xml" + cwd directory + user "tile" + group "tile" + not_if do + File.exist?("#{directory}/project.xml") and + File.mtime("#{directory}/project.xml") >= File.mtime("#{directory}/project.mml") + end + notifies :restart, resources(:service => "renderd") + end +end + +package "postgis" + +postgresql_user "jburgess" do + cluster node[:tile][:database][:cluster] + superuser true +end + +postgresql_user "tomh" do + cluster node[:tile][:database][:cluster] + superuser true +end + +postgresql_user "tile" do + cluster node[:tile][:database][:cluster] +end + +postgresql_user "www-data" do + cluster node[:tile][:database][:cluster] +end + +postgresql_database "gis" do + cluster node[:tile][:database][:cluster] + owner "tile" +end + +postgresql_extension "postgis" do + cluster node[:tile][:database][:cluster] + database "gis" +end + +[ "geography_columns", + "planet_osm_nodes", + "planet_osm_rels", + "planet_osm_ways", + "raster_columns", + "raster_overviews", + "spatial_ref_sys" ].each do |table| + postgresql_table table do + cluster node[:tile][:database][:cluster] + database "gis" + owner "tile" + permissions "tile" => :all + end +end + +[ "geometry_columns", + "planet_osm_line", + "planet_osm_point", + "planet_osm_polygon", + "planet_osm_roads" ].each do |table| + postgresql_table table do + cluster node[:tile][:database][:cluster] + database "gis" + owner "tile" + permissions "tile" => :all, "www-data" => :select + end +end + +postgresql_munin "gis" do + cluster node[:tile][:database][:cluster] + database "gis" +end + +#if node[:tile][:node_file] +# file node[:tile][:node_file] do +# owner "tile" +# group "tile" +# mode 0664 +# end +#end + +package "osm2pgsql" +package "osmosis" + +package "ruby" +package "rubygems" + +package "libproj-dev" +package "libxml2-dev" +package "libpq-dev" + +gem_package "proj4rb" +gem_package "libxml-ruby" +gem_package "pg" + +remote_directory "/usr/local/lib/site_ruby" do + source "ruby" + owner "root" + group "root" + mode 0755 + files_owner "root" + files_group "root" + files_mode 0644 +end + +template "/usr/local/bin/expire-tiles" do + source "expire-tiles.erb" + owner "root" + group "root" + mode 0755 +end + +directory "/var/lib/replicate" do + owner "tile" + group "tile" + mode 0755 +end + +directory "/var/log/replicate" do + owner "tile" + group "tile" + mode 0755 +end + +template "/var/lib/replicate/configuration.txt" do + source "replicate.configuration.erb" + owner "tile" + group "tile" + mode 0644 +end + +template "/usr/local/bin/replicate" do + source "replicate.erb" + owner "root" + group "root" + mode 0755 +end + +template "/etc/init.d/replicate" do + source "replicate.init.erb" + owner "root" + group "root" + mode 0755 +end + +service "replicate" do + action [ :enable, :start ] + supports :restart => true + subscribes :restart, resources(:template => "/usr/local/bin/replicate") + subscribes :restart, resources(:template => "/etc/init.d/replicate") +end + +template "/etc/logrotate.d/replicate" do + source "replicate.logrotate.erb" + owner "root" + group "root" + mode 0644 +end + +munin_plugin "mod_tile_fresh" +munin_plugin "mod_tile_response" +munin_plugin "mod_tile_zoom" + +munin_plugin "renderd_processed" +munin_plugin "renderd_queue" +munin_plugin "renderd_zoom" +munin_plugin "renderd_zoom_time" + +munin_plugin "replication_delay" do + conf "munin.erb" +end + diff --git a/cookbooks/tile/templates/default/apache.erb b/cookbooks/tile/templates/default/apache.erb new file mode 100644 index 000000000..024c664be --- /dev/null +++ b/cookbooks/tile/templates/default/apache.erb @@ -0,0 +1,46 @@ +# DO NOT EDIT - This file is being maintained by Chef + + + # Basic server configuration + ServerName <%= node[:fqdn] %> + ServerAlias tile.openstreetmap.org + ServerAlias parent.tile.openstreetmap.org + ServerAdmin webmaster@openstreetmap.org + + # Configure location of static files and CGI scripts + DocumentRoot /srv/tile.openstreetmap.org/html + ScriptAlias /cgi-bin/ /srv/tile.openstreetmap.org/cgi-bin/ + + # Get the real remote IP for requests via a trusted proxy + RemoteIPHeader X-Forwarded-For +<% @caches.each do |cache| -%> +<% cache.ipaddresses(:role => :external) do |address| -%> + RemoteIPTrustedProxy <%= address %> +<% end -%> +<% end -%> + + # Setup logging + CustomLog /var/log/apache2/access.log combined + ErrorLog /var/log/apache2/error.log + BufferedLogs on + + # Enable the rewrite engine + RewriteEngine on + + # Rewrite tile requests to the default style + RewriteRule ^/(-?\d+)/(-?\d+)/(-?\d+)\.png$ /default/$1/$2/$3.png [PT,T=image/png,L] + + + + Options None + AllowOverride None + Order allow,deny + Allow from all + + + + Options ExecCGI + AllowOverride None + Order allow,deny + Allow from all + diff --git a/cookbooks/tile/templates/default/expire-tiles.erb b/cookbooks/tile/templates/default/expire-tiles.erb new file mode 100644 index 000000000..e9473a1a7 --- /dev/null +++ b/cookbooks/tile/templates/default/expire-tiles.erb @@ -0,0 +1,9 @@ +#!/usr/bin/ruby + +# DO NOT EDIT - This file is being maintained by Chef + +require 'expire' + +ARGV.each do |f| + Expire::expire(f) +end diff --git a/cookbooks/tile/templates/default/export.erb b/cookbooks/tile/templates/default/export.erb new file mode 100755 index 000000000..8dd209ba6 --- /dev/null +++ b/cookbooks/tile/templates/default/export.erb @@ -0,0 +1,150 @@ +#!/usr/bin/python -u +# -*- coding: utf-8 -*- + +import cairo +import cgi +import mapnik2 +import os +import shutil +import sys +import tempfile +import resource + +# Limit maximum CPU time +# The Postscript output format can sometimes take hours +resource.setrlimit(resource.RLIMIT_CPU,(180,180)) + +# Limit memory usage +# Some odd requests can cause extreme memory usage +resource.setrlimit(resource.RLIMIT_AS,(4000000000, 4000000000)) + +# Routine to output HTTP headers +def output_headers(content_type, filename = "", length = 0): + print "Content-Type: %s" % content_type + if filename: + print "Content-Disposition: attachment; filename=\"%s\"" % filename + if length: + print "Content-Length: %d" % length + print "" + +# Routine to output the contents of a file +def output_file(file): + file.seek(0) + shutil.copyfileobj(file, sys.stdout) + +# Routine to get the size of a file +def file_size(file): + return os.fstat(file.fileno()).st_size + +# Routine to report an error +def output_error(message): + output_headers("text/html") + print "" + print "" + print "Error" + print "" + print "" + print "

Error

" + print "

%s

" % message + print "" + print "" + +# Parse CGI parameters +form = cgi.FieldStorage() + +# Make sure we have a user agent +if not os.environ.has_key('HTTP_USER_AGENT'): + os.environ['HTTP_USER_AGENT'] = 'NONE' + +# Get the load average +loadavg = float(open("/proc/loadavg").readline().split(" ")[0]) + +# Process the request +if loadavg > 35.0: + # Abort if the load average on the machine is too high + print "Status: 503 Service Unavailable" + output_error("The load average on the server is too high at the moment. Please wait a few minutes before trying again.") +<% @blocks["user_agents"].each do |user_agent| -%> +elif os.environ['HTTP_USER_AGENT'] == '<%= user_agent %>': + # Block scraper + print "Status: 503 Service Unavailable" + output_error("The load average on the server is too high at the moment. Please wait a few minutes before trying again.") +<% end -%> +elif not form.has_key("bbox"): + # No bounding box specified + output_error("No bounding box specified") +elif not form.has_key("scale"): + # No scale specified + output_error("No scale specified") +elif not form.has_key("format"): + # No format specified + output_error("No format specified") +else: + # Create projection object + prj = mapnik2.Projection("+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs +over"); + + # Get the bounds of the area to render + bbox = [float(x) for x in form.getvalue("bbox").split(",")] + + if bbox[0] >= bbox[2] or bbox[1] >= bbox[3]: + # Bogus bounding box + output_error("Invalid bounding box") + else: + # Project the bounds to the map projection + bbox = mapnik2.forward_(mapnik2.Box2d(*bbox), prj) + + # Calculate the size of the final rendered image + scale = float(form.getvalue("scale")) + width = int(bbox.width() / scale / 0.00028) + height = int(bbox.height() / scale / 0.00028) + + # Limit the size of map we are prepared to produce + if width * height > 4000000: + # Map is too large (limit is approximately A2 size) + output_error("Map too large") + else: + # Create map + map = mapnik2.Map(width, height) + + # Load map configuration + mapnik2.load_map(map, "/home/jburgess/live/osm2.xml") + + # Zoom the map to the bounding box + map.zoom_to_box(bbox) + + # Render the map + if form.getvalue("format") == "png": + image = mapnik2.Image(map.width, map.height) + mapnik2.render(map, image) + png = image.tostring("png") + output_headers("image/png", "map.png", len(png)) + sys.stdout.write(png) + elif form.getvalue("format") == "jpeg": + image = mapnik2.Image(map.width, map.height) + mapnik2.render(map, image) + jpeg = image.tostring("jpeg") + output_headers("image/jpeg", "map.jpg", len(jpeg)) + sys.stdout.write(jpeg) + elif form.getvalue("format") == "svg": + file = tempfile.NamedTemporaryFile() + surface = cairo.SVGSurface(file.name, map.width, map.height) + mapnik2.render(map, surface) + surface.finish() + output_headers("image/svg+xml", "map.svg", file_size(file)) + output_file(file) + elif form.getvalue("format") == "pdf": + file = tempfile.NamedTemporaryFile() + surface = cairo.PDFSurface(file.name, map.width, map.height) + mapnik2.render(map, surface) + surface.finish() + output_headers("application/pdf", "map.pdf", file_size(file)) + output_file(file) + elif form.getvalue("format") == "ps": + file = tempfile.NamedTemporaryFile() + surface = cairo.PSSurface(file.name, map.width, map.height) + mapnik2.render(map, surface) + surface.finish() + output_headers("application/postscript", "map.ps", file_size(file)) + output_file(file) + else: + output_error("Unknown format '%s'" % form.getvalue("format")) diff --git a/cookbooks/tile/templates/default/logrotate.apache.erb b/cookbooks/tile/templates/default/logrotate.apache.erb new file mode 100644 index 000000000..9a82d4ca8 --- /dev/null +++ b/cookbooks/tile/templates/default/logrotate.apache.erb @@ -0,0 +1,16 @@ +# DO NOT EDIT - This file is being maintained by Chef + +/var/log/apache2/*.log { + daily + size 1G + missingok + rotate 52 + compress + delaycompress + notifempty + create 640 root adm + sharedscripts + postrotate + /usr/bin/service apache2 reload > /dev/null + endscript +} diff --git a/cookbooks/tile/templates/default/munin.erb b/cookbooks/tile/templates/default/munin.erb new file mode 100644 index 000000000..b789827b2 --- /dev/null +++ b/cookbooks/tile/templates/default/munin.erb @@ -0,0 +1,4 @@ +# DO NOT EDIT - This file is being maintained by Chef + +[<%= @name %>] +env.state /var/lib/replicate/state.txt diff --git a/cookbooks/tile/templates/default/renderd.conf.erb b/cookbooks/tile/templates/default/renderd.conf.erb new file mode 100644 index 000000000..ebd8c14eb --- /dev/null +++ b/cookbooks/tile/templates/default/renderd.conf.erb @@ -0,0 +1,19 @@ +# DO NOT EDIT - This file is being maintained by Chef + +[renderd] +socketname=/var/run/renderd/renderd.sock +num_threads=<%= node[:cpu][:total] - 2 %> +tile_dir=/srv/tile.openstreetmap.org/tiles +stats_file=/var/run/renderd/renderd.stats + +[mapnik] +plugins_dir=/usr/lib/mapnik/input +font_dir=/usr/share/fonts +font_dir_recurse=true +<% node[:tile][:styles].each do |name,details| -%> + +[<%= name %>] +URI=/<%= name %>/ +XML=/srv/tile.openstreetmap.org/styles/<%= name %>/project.xml +HOST=tile.openstreetmap.org +<% end -%> diff --git a/cookbooks/tile/templates/default/replicate.configuration.erb b/cookbooks/tile/templates/default/replicate.configuration.erb new file mode 100644 index 000000000..8dc015c0c --- /dev/null +++ b/cookbooks/tile/templates/default/replicate.configuration.erb @@ -0,0 +1,8 @@ +# DO NOT EDIT - This file is being maintained by Chef + +# The URL of the directory containing change files. +baseUrl=http://planet.openstreetmap.org/replication/minute + +# Defines the maximum time interval in seconds to download in a single invocation. +# Setting to 0 disables this feature. +maxInterval = 3600 diff --git a/cookbooks/tile/templates/default/replicate.erb b/cookbooks/tile/templates/default/replicate.erb new file mode 100644 index 000000000..862b6a5e7 --- /dev/null +++ b/cookbooks/tile/templates/default/replicate.erb @@ -0,0 +1,77 @@ +#!/bin/bash + +# DO NOT EDIT - This file is being maintained by Chef + +# Initialize timestamp with day of latest planet dump +# Setting to midnight ensures we get conistent data after first run +# osmosis --read-replication-interval-init + +# Send output to the log +exec > /var/log/replicate/replicate.log 2>&1 + +# Change to the replication state directory +cd /var/lib/replicate + +# Read in initial state +. state.txt + +# Loop indefinitely +while true +do + # Work out the name of the next file + file="changes-${sequenceNumber}.osm.gz" + + # Fetch the next set of changes + osmosis --read-replication-interval --simc --write-xml-change file="${file}" compressionMethod="gzip" + + # Check for errors + if [ $? -eq 0 ] + then + # Enable exit on error + set -e + + # Remember the previous sequence number + prevSequenceNumber=$sequenceNumber + + # Read in new state + . state.txt + + # Did we get any new data? + if [ "${sequenceNumber}" == "${prevSequenceNumber}" ] + then + # Log the lack of data + echo "No new data available. Sleeping..." + + # Remove file, it will just be an empty changeset + rm ${file} + + # Sleep for a short while + sleep 30 + else + # Log the new data + echo "Fetched new data from ${prevSequenceNumber} to ${sequenceNumber} into ${file}" + + # Apply the changes to the database +<% if node[:tile][:node_file] -%> + osm2pgsql --slim --append --flat-nodes=<%= node[:tile][:node_file] %> ${file} +<% else -%> + osm2pgsql --slim --append ${file} +<% end -%> + + # Expire tiles which are touched by the changes + /usr/local/bin/expire-tiles ${file} > /dev/null 2>&1 & + fi + + # Delete old downloads + find . -name 'changes-*.gz' -mmin +300 -exec rm -f {} \; + + # Disable exit on error + set +e + else + # Log our failure to fetch changes + echo "Failed to fetch changes - waiting a few minutes before retry" + + # Wait five minutes and have another go + sleep 300 + fi +done diff --git a/cookbooks/tile/templates/default/replicate.init.erb b/cookbooks/tile/templates/default/replicate.init.erb new file mode 100644 index 000000000..d2200f87a --- /dev/null +++ b/cookbooks/tile/templates/default/replicate.init.erb @@ -0,0 +1,24 @@ +#!/bin/bash + +# DO NOT EDIT - This file is being maintained by Chef + +start() { + start-stop-daemon --start --chuid tile --background --make-pidfile -pidfile /var/run/replicate.pid --exec /usr/local/bin/replicate +} + +stop() { + start-stop-daemon --stop --retry 300 --pidfile /var/run/replicate.pid --exec /usr/local/bin/replicate +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop || exit $? + start + ;; +esac diff --git a/cookbooks/tile/templates/default/replicate.logrotate.erb b/cookbooks/tile/templates/default/replicate.logrotate.erb new file mode 100644 index 000000000..a2878af92 --- /dev/null +++ b/cookbooks/tile/templates/default/replicate.logrotate.erb @@ -0,0 +1,10 @@ +# DO NOT EDIT - This file is being maintained by Chef + +/var/log/replicate/*.log { + compress + delaycompress + notifempty + postrotate + /usr/bin/service replicate restart + endscript +} diff --git a/cookbooks/tile/templates/default/tile.conf.erb b/cookbooks/tile/templates/default/tile.conf.erb new file mode 100644 index 000000000..9748774c2 --- /dev/null +++ b/cookbooks/tile/templates/default/tile.conf.erb @@ -0,0 +1,33 @@ +# DO NOT EDIT - This file is being maintained by Chef + +# Set location of renderd socket +ModTileRenderdSocketName /var/run/renderd/renderd.sock + +# Set location of tile directory +ModTileTileDir /srv/tile.openstreetmap.org/tiles + +# Time to wait for a re-render before serving a dirty tile +ModTileRequestTimeout 3 + +# Don't try and re-render dirty tiles if the load is higher than this +ModTileMaxLoadOld 36 + +# Don't try and render missing tiles if the load is higher than this +ModTileMaxLoadOld 72 + +# Maximum expiry to set on a tile +ModTileCacheDurationMax 604800 + +# Expiry time for dirty tiles that have been queued for re-rendering +ModTileCacheDurationDirty 900 + +# Minimum expiry time for fresh tiles +ModTileCacheDurationMinimum 10800 +ModTileCacheDurationMediumZoom 13 86400 +ModTileCacheDurationLowZoom 9 518400 + +# Factor controlling effect of last modification time on expiry +ModTileCacheLastModifiedFactor 0.20 + +# Load tile configuration +LoadTileConfigFile /etc/renderd.conf -- 2.43.2