Add tile cookbook
authorTom Hughes <tom@compton.nu>
Mon, 17 Jun 2013 22:08:37 +0000 (23:08 +0100)
committerTom Hughes <tom@compton.nu>
Mon, 17 Jun 2013 22:08:37 +0000 (23:08 +0100)
22 files changed:
cookbooks/tile/README.rdoc [new file with mode: 0644]
cookbooks/tile/attributes/default.rb [new file with mode: 0644]
cookbooks/tile/files/default/html/abuse.png [new file with mode: 0644]
cookbooks/tile/files/default/html/abuse2.png [new file with mode: 0644]
cookbooks/tile/files/default/html/abuse3.png [new file with mode: 0644]
cookbooks/tile/files/default/html/clientaccesspolicy.xml [new file with mode: 0644]
cookbooks/tile/files/default/html/favicon.ico [new file with mode: 0644]
cookbooks/tile/files/default/html/update-url-tile.png [new file with mode: 0644]
cookbooks/tile/files/default/ruby/expire.rb [new file with mode: 0755]
cookbooks/tile/metadata.rb [new file with mode: 0644]
cookbooks/tile/recipes/default.rb [new file with mode: 0644]
cookbooks/tile/templates/default/apache.erb [new file with mode: 0644]
cookbooks/tile/templates/default/expire-tiles.erb [new file with mode: 0644]
cookbooks/tile/templates/default/export.erb [new file with mode: 0755]
cookbooks/tile/templates/default/logrotate.apache.erb [new file with mode: 0644]
cookbooks/tile/templates/default/munin.erb [new file with mode: 0644]
cookbooks/tile/templates/default/renderd.conf.erb [new file with mode: 0644]
cookbooks/tile/templates/default/replicate.configuration.erb [new file with mode: 0644]
cookbooks/tile/templates/default/replicate.erb [new file with mode: 0644]
cookbooks/tile/templates/default/replicate.init.erb [new file with mode: 0644]
cookbooks/tile/templates/default/replicate.logrotate.erb [new file with mode: 0644]
cookbooks/tile/templates/default/tile.conf.erb [new file with mode: 0644]

diff --git a/cookbooks/tile/README.rdoc b/cookbooks/tile/README.rdoc
new file mode 100644 (file)
index 0000000..3de2ec7
--- /dev/null
@@ -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 (file)
index 0000000..79d55d1
--- /dev/null
@@ -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 (file)
index 0000000..f9f7910
Binary files /dev/null and b/cookbooks/tile/files/default/html/abuse.png differ
diff --git a/cookbooks/tile/files/default/html/abuse2.png b/cookbooks/tile/files/default/html/abuse2.png
new file mode 100644 (file)
index 0000000..ba40a46
Binary files /dev/null and b/cookbooks/tile/files/default/html/abuse2.png differ
diff --git a/cookbooks/tile/files/default/html/abuse3.png b/cookbooks/tile/files/default/html/abuse3.png
new file mode 100644 (file)
index 0000000..b297ae7
Binary files /dev/null and b/cookbooks/tile/files/default/html/abuse3.png differ
diff --git a/cookbooks/tile/files/default/html/clientaccesspolicy.xml b/cookbooks/tile/files/default/html/clientaccesspolicy.xml
new file mode 100644 (file)
index 0000000..d60ec1d
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<access-policy>
+  <cross-domain-access>
+    <policy>
+      <allow-from http-request-headers="*">
+        <domain uri="*"/>
+      </allow-from>
+      <grant-to>
+        <resource path="/" include-subpaths="true"/>
+      </grant-to>
+    </policy>
+  </cross-domain-access>
+</access-policy>
diff --git a/cookbooks/tile/files/default/html/favicon.ico b/cookbooks/tile/files/default/html/favicon.ico
new file mode 100644 (file)
index 0000000..27b042b
Binary files /dev/null and b/cookbooks/tile/files/default/html/favicon.ico differ
diff --git a/cookbooks/tile/files/default/html/update-url-tile.png b/cookbooks/tile/files/default/html/update-url-tile.png
new file mode 100644 (file)
index 0000000..3456d30
Binary files /dev/null and b/cookbooks/tile/files/default/html/update-url-tile.png differ
diff --git a/cookbooks/tile/files/default/ruby/expire.rb b/cookbooks/tile/files/default/ruby/expire.rb
new file mode 100755 (executable)
index 0000000..1c2993b
--- /dev/null
@@ -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 (file)
index 0000000..6d261cf
--- /dev/null
@@ -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 (file)
index 0000000..d60d52c
--- /dev/null
@@ -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 (file)
index 0000000..024c664
--- /dev/null
@@ -0,0 +1,46 @@
+# DO NOT EDIT - This file is being maintained by Chef
+
+<VirtualHost *:80>
+  # 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]
+</VirtualHost>
+
+<Directory /srv/tile.openstreetmap.org/html>
+  Options None
+  AllowOverride None
+  Order allow,deny
+  Allow from all
+</Directory>
+
+<Directory /srv/tile.openstreetmap.org/cgi-bin>
+  Options ExecCGI
+  AllowOverride None
+  Order allow,deny
+  Allow from all
+</Directory>
diff --git a/cookbooks/tile/templates/default/expire-tiles.erb b/cookbooks/tile/templates/default/expire-tiles.erb
new file mode 100644 (file)
index 0000000..e9473a1
--- /dev/null
@@ -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 (executable)
index 0000000..8dd209b
--- /dev/null
@@ -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 "<html>"
+  print "<head>"
+  print "<title>Error</title>"
+  print "</head>"
+  print "<body>"
+  print "<h1>Error</h1>"
+  print "<p>%s</p>" % message
+  print "</body>"
+  print "</html>"
+
+# 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 (file)
index 0000000..9a82d4c
--- /dev/null
@@ -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 (file)
index 0000000..b789827
--- /dev/null
@@ -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 (file)
index 0000000..ebd8c14
--- /dev/null
@@ -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 (file)
index 0000000..8dc015c
--- /dev/null
@@ -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 (file)
index 0000000..862b6a5
--- /dev/null
@@ -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 (file)
index 0000000..d2200f8
--- /dev/null
@@ -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 (file)
index 0000000..a2878af
--- /dev/null
@@ -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 (file)
index 0000000..9748774
--- /dev/null
@@ -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