-- Data processing based on openmaptiles.org schema
-- https://openmaptiles.org/schema/
-- Copyright (c) 2016, KlokanTech.com & OpenMapTiles contributors.
-- Used under CC-BY 4.0

--------
-- Alter these lines to control which languages are written for place/streetnames
--
-- Preferred language can be (for example) "en" for English, "de" for German, or nil to use OSM's name tag:
preferred_language = en
-- This is written into the following vector tile attribute (usually "name:latin"):
preferred_language_attribute = "name:latin"
-- If OSM's name tag differs, then write it into this attribute (usually "name_int"):
default_language_attribute = "name_int"
-- Also write these languages if they differ - for example, { "de", "fr" }
additional_languages = {}
--------

-- Enter/exit Tilemaker
function init_function()
end

function exit_function()
end

-- Implement Sets in tables
function Set(list)
	local set = {}
	for _, l in ipairs(list) do set[l] = true end
	return set
end

-- Meters per pixel if tile is 256x256
ZRES5                 = 4891.97
ZRES6                 = 2445.98
ZRES7                 = 1222.99
ZRES8                 = 611.5
ZRES9                 = 305.7
ZRES10                = 152.9
ZRES11                = 76.4
ZRES12                = 38.2
ZRES13                = 19.1

-- The height of one floor, in meters
BUILDING_FLOOR_HEIGHT = 3.66

-- Process node/way tags
aerodromeValues       = Set { "international", "public", "regional", "military", "private" }

-- Process node tags

node_keys             = { "addr:housenumber", "aerialway", "aeroway", "amenity", "barrier", "highway", "historic",
	"leisure", "natural", "office", "place", "railway", "shop", "sport", "tourism", "waterway" }
function node_function(node)
	-- Write 'aerodrome_label'
	local aeroway = node:Find("aeroway")
	if aeroway == "aerodrome" then
		node:Layer("aerodrome_label", false)
		SetNameAttributes(node)
		node:Attribute("iata", node:Find("iata"))
		SetEleAttributes(node)
		node:Attribute("icao", node:Find("icao"))

		local aerodrome_value = node:Find("aerodrome")
		local class
		if aerodromeValues[aerodrome_value] then class = aerodrome_value else class = "other" end
		node:Attribute("class", class)
	end
	-- Write 'housenumber'
	local housenumber = node:Find("addr:housenumber")
	if housenumber ~= "" then
		node:Layer("housenumber", false)
		node:Attribute("housenumber", housenumber)
	end

	-- Write 'place'
	-- note that OpenMapTiles has a rank for countries (1-3), states (1-6) and cities (1-10+);
	--   we could potentially approximate it for cities based on the population tag
	local place = node:Find("place")
	if place ~= "" then
		local rank = nil
		local mz = 13
		local pop = tonumber(node:Find("population")) or 0

		if place == "continent" then
			mz = 0
		elseif place == "country" then
			if pop > 50000000 then
				rank = 1;
				mz = 1
			elseif pop > 20000000 then
				rank = 2;
				mz = 2
			else
				rank = 3;
				mz = 3
			end
		elseif place == "state" then
			mz = 4
		elseif place == "city" then
			mz = 5
		elseif place == "town" and pop > 8000 then
			mz = 7
		elseif place == "town" then
			mz = 8
		elseif place == "village" and pop > 2000 then
			mz = 9
		elseif place == "village" then
			mz = 10
		elseif place == "suburb" then
			mz = 11
		elseif place == "hamlet" then
			mz = 12
		elseif place == "neighbourhood" then
			mz = 13
		elseif place == "locality" then
			mz = 13
		end

		node:Layer("place", false)
		node:Attribute("class", place)
		node:MinZoom(mz)
		if rank then node:AttributeNumeric("rank", rank) end
		if place == "country" then node:Attribute("iso_a2", node:Find("ISO3166-1:alpha2")) end
		SetNameAttributes(node)
		return
	end

	-- Write 'poi'
	local rank, class, subclass = GetPOIRank(node)
	if rank then WritePOI(node, class, subclass, rank) end

	-- Write 'mountain_peak' and 'water_name'
	local natural = node:Find("natural")
	if natural == "peak" or natural == "volcano" then
		node:Layer("mountain_peak", false)
		SetEleAttributes(node)
		node:AttributeNumeric("rank", 1)
		node:Attribute("class", natural)
		SetNameAttributes(node)
		return
	end
	if natural == "bay" then
		node:Layer("water_name", false)
		SetNameAttributes(node)
		return
	end
end

-- Process way tags

majorRoadValues    = Set { "motorway", "trunk", "primary" }
mainRoadValues     = Set { "secondary", "motorway_link", "trunk_link", "primary_link", "secondary_link" }
midRoadValues      = Set { "tertiary", "tertiary_link" }
minorRoadValues    = Set { "unclassified", "residential", "road", "living_street" }
trackValues        = Set { "track" }
pathValues         = Set { "footway", "cycleway", "bridleway", "path", "steps", "pedestrian" }
linkValues         = Set { "motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link" }
constructionValues = Set { "primary", "secondary", "tertiary", "motorway", "service", "trunk", "track" }
pavedValues        = Set { "paved", "asphalt", "cobblestone", "concrete", "concrete:lanes", "concrete:plates", "metal",
	"paving_stones", "sett", "unhewn_cobblestone", "wood" }
unpavedValues      = Set { "unpaved", "compacted", "dirt", "earth", "fine_gravel", "grass", "grass_paver", "gravel",
	"gravel_turf", "ground", "ice", "mud", "pebblestone", "salt", "sand", "snow", "woodchips" }

aerowayBuildings   = Set { "terminal", "gate", "tower" }
landuseKeys        = Set { "school", "university", "kindergarten", "college", "library", "hospital",
	"railway", "cemetery", "military", "residential", "commercial", "industrial",
	"retail", "stadium", "pitch", "playground", "theme_park", "bus_station", "zoo" }
landcoverKeys      = {
	wood = "wood",
	forest = "wood",
	wetland = "wetland",
	beach = "sand",
	sand = "sand",
	farmland = "farmland",
	farm = "farmland",
	orchard = "farmland",
	vineyard = "farmland",
	plant_nursery = "farmland",
	glacier = "ice",
	ice_shelf = "ice",
	grassland = "grass",
	grass = "grass",
	meadow = "grass",
	allotments = "grass",
	park = "grass",
	village_green = "grass",
	recreation_ground = "grass",
	garden = "grass",
	golf_course = "grass"
}

-- POI key/value pairs: based on https://github.com/openmaptiles/openmaptiles/blob/master/layers/poi/mapping.yaml
poiTags            = {
	aerialway = Set { "station" },
	amenity = Set { "arts_centre", "bank", "bar", "bbq", "bicycle_parking", "bicycle_rental", "biergarten", "bus_station",
		"cafe", "cinema", "clinic", "college", "community_centre", "courthouse", "dentist", "doctors", "embassy", "fast_food",
		"ferry_terminal", "fire_station", "food_court", "fuel", "grave_yard", "hospital", "ice_cream", "kindergarten",
		"library", "marketplace", "motorcycle_parking", "nightclub", "nursing_home", "parking", "pharmacy",
		"place_of_worship", "police", "post_box", "post_office", "prison", "pub", "public_building", "recycling",
		"restaurant", "school", "shelter", "swimming_pool", "taxi", "telephone", "theatre", "toilets", "townhall",
		"university", "veterinary", "waste_basket" },
	barrier = Set { "bollard", "border_control", "cycle_barrier", "gate", "lift_gate", "sally_port", "stile", "toll_booth" },
	building = Set { "dormitory" },
	highway = Set { "bus_stop" },
	historic = Set { "monument", "castle", "ruins" },
	landuse = Set { "basin", "brownfield", "cemetery", "reservoir", "winter_sports" },
	leisure = Set { "dog_park", "escape_game", "garden", "golf_course", "ice_rink", "hackerspace", "marina",
		"miniature_golf", "park", "pitch", "playground", "sports_centre", "stadium", "swimming_area", "swimming_pool",
		"water_park" },
	railway = Set { "halt", "station", "subway_entrance", "train_station_entrance", "tram_stop" },
	shop = Set { "accessories", "alcohol", "antiques", "art", "bag", "bakery", "beauty", "bed", "beverages", "bicycle",
		"books", "boutique", "butcher", "camera", "car", "car_repair", "carpet", "charity", "chemist", "chocolate", "clothes",
		"coffee", "computer", "confectionery", "convenience", "copyshop", "cosmetics", "deli", "delicatessen",
		"department_store", "doityourself", "dry_cleaning", "electronics", "erotic", "fabric", "florist", "frozen_food",
		"furniture", "garden_centre", "general", "gift", "greengrocer", "hairdresser", "hardware", "hearing_aids", "hifi",
		"ice_cream", "interior_decoration", "jewelry", "kiosk", "lamps", "laundry", "mall", "massage", "mobile_phone",
		"motorcycle", "music", "musical_instrument", "newsagent", "optician", "outdoor", "perfume", "perfumery", "pet",
		"photo", "second_hand", "shoes", "sports", "stationery", "supermarket", "tailor", "tattoo", "ticket", "tobacco",
		"toys", "travel_agency", "video", "video_games", "watches", "weapons", "wholesale", "wine" },
	sport = Set { "american_football", "archery", "athletics", "australian_football", "badminton", "baseball", "basketball",
		"beachvolleyball", "billiards", "bmx", "boules", "bowls", "boxing", "canadian_football", "canoe", "chess", "climbing",
		"climbing_adventure", "cricket", "cricket_nets", "croquet", "curling", "cycling", "disc_golf", "diving", "dog_racing",
		"equestrian", "fatsal", "field_hockey", "free_flying", "gaelic_games", "golf", "gymnastics", "handball", "hockey",
		"horse_racing", "horseshoes", "ice_hockey", "ice_stock", "judo", "karting", "korfball", "long_jump",
		"model_aerodrome", "motocross", "motor", "multi", "netball", "orienteering", "paddle_tennis", "paintball",
		"paragliding", "pelota", "racquet", "rc_car", "rowing", "rugby", "rugby_league", "rugby_union", "running", "sailing",
		"scuba_diving", "shooting", "shooting_range", "skateboard", "skating", "skiing", "soccer", "surfing", "swimming",
		"table_soccer", "table_tennis", "team_handball", "tennis", "toboggan", "volleyball", "water_ski", "yoga" },
	tourism = Set { "alpine_hut", "aquarium", "artwork", "attraction", "bed_and_breakfast", "camp_site", "caravan_site",
		"chalet", "gallery", "guest_house", "hostel", "hotel", "information", "motel", "museum", "picnic_site", "theme_park",
		"viewpoint", "zoo" },
	waterway = Set { "dock" }
}

-- POI "class" values: based on https://github.com/openmaptiles/openmaptiles/blob/master/layers/poi/poi.yaml
poiClasses         = {
	townhall = "town_hall",
	public_building = "town_hall",
	courthouse = "town_hall",
	community_centre = "town_hall",
	golf = "golf",
	golf_course = "golf",
	miniature_golf = "golf",
	fast_food = "fast_food",
	food_court = "fast_food",
	park = "park",
	bbq = "park",
	bus_stop = "bus",
	bus_station = "bus",
	subway_entrance = "entrance",
	train_station_entrance = "entrance",
	camp_site = "campsite",
	caravan_site = "campsite",
	laundry = "laundry",
	dry_cleaning = "laundry",
	supermarket = "grocery",
	deli = "grocery",
	delicatessen = "grocery",
	department_store = "grocery",
	greengrocer = "grocery",
	marketplace = "grocery",
	books = "library",
	library = "library",
	university = "college",
	college = "college",
	hotel = "lodging",
	motel = "lodging",
	bed_and_breakfast = "lodging",
	guest_house = "lodging",
	hostel = "lodging",
	chalet = "lodging",
	alpine_hut = "lodging",
	dormitory = "lodging",
	chocolate = "ice_cream",
	confectionery = "ice_cream",
	post_box = "post",
	post_office = "post",
	cafe = "cafe",
	school = "school",
	kindergarten = "school",
	alcohol = "alcohol_shop",
	beverages = "alcohol_shop",
	wine = "alcohol_shop",
	bar = "bar",
	nightclub = "bar",
	marina = "harbor",
	dock = "harbor",
	car = "car",
	car_repair = "car",
	taxi = "car",
	hospital = "hospital",
	nursing_home = "hospital",
	clinic = "hospital",
	grave_yard = "cemetery",
	cemetery = "cemetery",
	attraction = "attraction",
	viewpoint = "attraction",
	biergarten = "beer",
	pub = "beer",
	music = "music",
	musical_instrument = "music",
	american_football = "stadium",
	stadium = "stadium",
	soccer = "stadium",
	art = "art_gallery",
	artwork = "art_gallery",
	gallery = "art_gallery",
	arts_centre = "art_gallery",
	bag = "clothing_store",
	clothes = "clothing_store",
	swimming_area = "swimming",
	swimming = "swimming",
	castle = "castle",
	ruins = "castle"
}
poiClassRanks      = {
	hospital = 1,
	railway = 2,
	bus = 3,
	attraction = 4,
	harbor = 5,
	college = 6,
	school = 7,
	stadium = 8,
	zoo = 9,
	town_hall = 10,
	campsite = 11,
	cemetery = 12,
	park = 13,
	library = 14,
	police = 15,
	post = 16,
	golf = 17,
	shop = 18,
	grocery = 19,
	fast_food = 20,
	clothing_store = 21,
	bar = 22
}
waterClasses       = Set { "river", "riverbank", "stream", "canal", "drain", "ditch", "dock" }
waterwayClasses    = Set { "stream", "river", "canal", "drain", "ditch" }

-- Scan relations for use in ways

function relation_scan_function(relation)
	if relation:Find("type") == "boundary" and relation:Find("boundary") == "administrative" then
		relation:Accept()
	end
end

-- Process way tags

function way_function(way)
	local route        = way:Find("route")
	local highway      = way:Find("highway")
	local waterway     = way:Find("waterway")
	local water        = way:Find("water")
	local building     = way:Find("building")
	local natural      = way:Find("natural")
	local historic     = way:Find("historic")
	local landuse      = way:Find("landuse")
	local leisure      = way:Find("leisure")
	local amenity      = way:Find("amenity")
	local aeroway      = way:Find("aeroway")
	local railway      = way:Find("railway")
	local service      = way:Find("service")
	local sport        = way:Find("sport")
	local shop         = way:Find("shop")
	local tourism      = way:Find("tourism")
	local man_made     = way:Find("man_made")
	local boundary     = way:Find("boundary")
	local isClosed     = way:IsClosed()
	local housenumber  = way:Find("addr:housenumber")
	local write_name   = false
	local construction = way:Find("construction")

	-- Miscellaneous preprocessing
	if way:Find("disused") == "yes" then return end
	if boundary ~= "" and way:Find("protection_title") == "National Forest" and way:Find("operator") == "United States Forest Service" then return end
	if highway == "proposed" then return end
	if aerowayBuildings[aeroway] then
		building = "yes";
		aeroway = ""
	end
	if landuse == "field" then landuse = "farmland" end
	if landuse == "meadow" and way:Find("meadow") == "agricultural" then landuse = "farmland" end

	-- Boundaries within relations
	local admin_level = 11
	local isBoundary = false
	while true do
		local rel = way:NextRelation()
		if not rel then break end
		isBoundary = true
		admin_level = math.min(admin_level, tonumber(way:FindInRelation("admin_level")) or 11)
	end

	-- Boundaries in ways
	if boundary == "administrative" then
		admin_level = math.min(admin_level, tonumber(way:Find("admin_level")) or 11)
		isBoundary = true
	end

	-- Administrative boundaries
	-- https://openmaptiles.org/schema/#boundary
	if isBoundary and not (way:Find("maritime") == "yes") then
		local mz = 0
		if admin_level >= 3 and admin_level < 5 then
			mz = 4
		elseif admin_level >= 5 and admin_level < 7 then
			mz = 8
		elseif admin_level == 7 then
			mz = 10
		elseif admin_level >= 8 then
			mz = 12
		end

		way:Layer("boundary", false)
		way:AttributeNumeric("admin_level", admin_level)
		way:MinZoom(mz)
		-- disputed status (0 or 1). some styles need to have the 0 to show it.
		local disputed = way:Find("disputed")
		if disputed == "yes" then
			way:AttributeNumeric("disputed", 1)
		else
			way:AttributeNumeric("disputed", 0)
		end
	end

	-- Roads ('transportation' and 'transportation_name', plus 'transportation_name_detail')
	if highway ~= "" then
		local access = way:Find("access")
		local surface = way:Find("surface")

		local h = highway
		local minzoom = 99
		local layer = "transportation"
		if majorRoadValues[highway] then minzoom = 4 end
		if highway == "trunk" then
			minzoom = 5
		elseif highway == "primary" then
			minzoom = 7
		end
		if mainRoadValues[highway] then minzoom = 9 end
		if midRoadValues[highway] then minzoom = 11 end
		if minorRoadValues[highway] then
			h = "minor";
			minzoom = 12
		end
		if trackValues[highway] then
			h = "track";
			minzoom = 14
		end
		if pathValues[highway] then
			h = "path";
			minzoom = 14
		end
		if h == "service" then minzoom = 12 end

		-- Links (ramp)
		local ramp = false
		if linkValues[highway] then
			splitHighway = split(highway, "_")
			highway = splitHighway[1];
			h = highway
			ramp = true
			minzoom = 11
		end

		-- Construction
		if highway == "construction" then
			if constructionValues[construction] then
				h = construction .. "_construction"
				if construction ~= "service" and construction ~= "track" then
					minzoom = 11
				else
					minzoom = 12
				end
			else
				h = "minor_construction"
				minzoom = 14
			end
		end

		-- Write to layer
		if minzoom <= 14 then
			way:Layer(layer, false)
			way:MinZoom(minzoom)
			SetZOrder(way)
			way:Attribute("class", h)
			SetBrunnelAttributes(way)
			if ramp then way:AttributeNumeric("ramp", 1) end
			if access == "private" or access == "no" then way:Attribute("access", "no") end
			if pavedValues[surface] then way:Attribute("surface", "paved") end
			if unpavedValues[surface] then way:Attribute("surface", "unpaved") end

			-- Service
			if highway == "service" and service ~= "" then way:Attribute("service", service) end

			local oneway = way:Find("oneway")
			if oneway == "yes" or oneway == "1" then
				way:AttributeNumeric("oneway", 1)
			end
			if oneway == "-1" then
				-- **** TODO
			end

			-- Write names
			if minzoom < 8 then
				minzoom = 8
			end
			if highway == "motorway" or highway == "trunk" then
				way:Layer("transportation_name", false)
				way:MinZoom(minzoom)
			elseif h == "minor" or h == "track" or h == "path" or h == "service" then
				way:Layer("transportation_name_detail", false)
				way:MinZoom(minzoom)
			else
				way:Layer("transportation_name_mid", false)
				way:MinZoom(minzoom)
			end
			SetNameAttributes(way)
			way:Attribute("class", h)
			way:Attribute("network", "road") -- **** could also be us-interstate, us-highway, us-state
			if h ~= highway then way:Attribute("subclass", highway) end
			local ref = way:Find("ref")
			if ref ~= "" then
				way:Attribute("ref", ref)
				way:AttributeNumeric("ref_length", ref:len())
			end
		end
	end

	-- Railways ('transportation' and 'transportation_name', plus 'transportation_name_detail')
	if railway ~= "" then
		way:Layer("transportation", false)
		way:Attribute("class", railway)
		SetZOrder(way)
		SetBrunnelAttributes(way)
		if service ~= "" then
			way:Attribute("service", service)
			way:MinZoom(12)
		else
			way:MinZoom(9)
		end

		way:Layer("transportation_name", false)
		SetNameAttributes(way)
		way:MinZoom(14)
		way:Attribute("class", "rail")
	end

	-- Pier
	if man_made == "pier" then
		way:Layer("transportation", isClosed)
		SetZOrder(way)
		way:Attribute("class", "pier")
		SetMinZoomByArea(way)
	end

	-- 'Ferry'
	if route == "ferry" then
		way:Layer("transportation", false)
		way:Attribute("class", "ferry")
		SetZOrder(way)
		way:MinZoom(9)
		SetBrunnelAttributes(way)

		way:Layer("transportation_name", false)
		SetNameAttributes(way)
		way:MinZoom(12)
		way:Attribute("class", "ferry")
	end

	-- 'Aeroway'
	if aeroway ~= "" then
		way:Layer("aeroway", isClosed)
		way:Attribute("class", aeroway)
		way:Attribute("ref", way:Find("ref"))
		write_name = true
	end

	-- 'aerodrome_label'
	if aeroway == "aerodrome" then
		way:LayerAsCentroid("aerodrome_label")
		SetNameAttributes(way)
		way:Attribute("iata", way:Find("iata"))
		SetEleAttributes(way)
		way:Attribute("icao", way:Find("icao"))

		local aerodrome = way:Find(aeroway)
		local class
		if aerodromeValues[aerodrome] then class = aerodrome else class = "other" end
		way:Attribute("class", class)
	end

	-- Set 'waterway' and associated
	if waterwayClasses[waterway] and not isClosed then
		if waterway == "river" and way:Holds("name") then
			way:Layer("waterway", false)
		else
			way:Layer("waterway_detail", false)
		end
		if way:Find("intermittent") == "yes" then
			way:AttributeNumeric("intermittent", 1)
		else
			way:AttributeNumeric(
				"intermittent", 0)
		end
		way:Attribute("class", waterway)
		SetNameAttributes(way)
		SetBrunnelAttributes(way)
	elseif waterway == "boatyard" then
		way:Layer("landuse", isClosed);
		way:Attribute("class", "industrial");
		way:MinZoom(12)
	elseif waterway == "dam" then
		way:Layer("building", isClosed)
	elseif waterway == "fuel" then
		way:Layer("landuse", isClosed);
		way:Attribute("class", "industrial");
		way:MinZoom(14)
	end
	-- Set names on rivers
	if waterwayClasses[waterway] and not isClosed then
		if waterway == "river" and way:Holds("name") then
			way:Layer("water_name", false)
		else
			way:Layer("water_name_detail", false)
			way:MinZoom(14)
		end
		way:Attribute("class", waterway)
		SetNameAttributes(way)
	end

	-- Set 'building' and associated
	if building ~= "" then
		way:Layer("building", true)
		SetBuildingHeightAttributes(way)
		SetMinZoomByArea(way)
	end

	-- Set 'housenumber'
	if housenumber ~= "" then
		way:LayerAsCentroid("housenumber", false)
		way:Attribute("housenumber", housenumber)
	end

	-- Set 'water'
	if natural == "water" or natural == "bay" or leisure == "swimming_pool" or landuse == "reservoir" or landuse == "basin" or waterClasses[waterway] then
		if way:Find("covered") == "yes" or not isClosed then return end
		local class = "lake";
		if natural == "bay" then class = "ocean" elseif waterway ~= "" then class = "river" end
		if class == "lake" and way:Find("wikidata") == "Q192770" then return end
		if class == "ocean" and isClosed and (way:AreaIntersecting("ocean") / way:Area() > 0.98) then return end
		way:Layer("water", true)
		SetMinZoomByArea(way)
		way:Attribute("class", class)

		if way:Find("intermittent") == "yes" then way:Attribute("intermittent", 1) end
		-- we only want to show the names of actual lakes not every man-made basin that probably doesn't even have a name other than "basin"
		-- examples for which we don't want to show a name:
		--  https://www.openstreetmap.org/way/25958687
		--  https://www.openstreetmap.org/way/27201902
		--  https://www.openstreetmap.org/way/25309134
		--  https://www.openstreetmap.org/way/24579306
		if way:Holds("name") and natural == "water" and water ~= "basin" and water ~= "wastewater" then
			way:LayerAsCentroid("water_name_detail")
			SetNameAttributes(way)
			SetMinZoomByArea(way)
			way:Attribute("class", class)
		end

		return -- in case we get any landuse processing
	end

	-- Set 'landcover' (from landuse, natural, leisure)
	local l = landuse
	if l == "" then l = natural end
	if l == "" then l = leisure end
	if landcoverKeys[l] then
		way:Layer("landcover", true)
		SetMinZoomByArea(way)
		way:Attribute("class", landcoverKeys[l])
		if l == "wetland" then
			way:Attribute("subclass", way:Find("wetland"))
		else
			way:Attribute("subclass", l)
		end
		write_name = true

		-- Set 'landuse'
	else
		if l == "" then l = amenity end
		if l == "" then l = tourism end
		if landuseKeys[l] then
			way:Layer("landuse", true)
			way:Attribute("class", l)
			if l == "residential" then
				if way:Area() < ZRES8 ^ 2 then
					way:MinZoom(8)
				else
					SetMinZoomByArea(way)
				end
			else
				way:MinZoom(11)
			end
			write_name = true
		end
	end

	-- Parks
	-- **** name?
	if boundary == "national_park" then
		way:Layer("park", true);
		way:Attribute("class", boundary);
		SetNameAttributes(way)
	elseif leisure == "nature_reserve" then
		way:Layer("park", true);
		way:Attribute("class", leisure);
		SetNameAttributes(way)
	end

	-- POIs ('poi' and 'poi_detail')
	local rank, class, subclass = GetPOIRank(way)
	if rank then
		WritePOI(way, class, subclass, rank);
		return
	end

	-- Catch-all
	if (building ~= "" or write_name) and way:Holds("name") then
		way:LayerAsCentroid("poi_detail")
		SetNameAttributes(way)
		if write_name then rank = 6 else rank = 25 end
		way:AttributeNumeric("rank", rank)
	end
end

-- Remap coastlines
function attribute_function(attr, layer)
	if attr["featurecla"] == "Glaciated areas" then
		return { subclass = "glacier" }
	elseif attr["featurecla"] == "Antarctic Ice Shelf" then
		return { subclass = "ice_shelf" }
	elseif attr["featurecla"] == "Urban area" then
		return { class = "residential" }
	else
		return { class = "ocean" }
	end
end

-- ==========================================================
-- Common functions

-- Write a way centroid to POI layer
function WritePOI(obj, class, subclass, rank)
	local layer = "poi"
	if rank > 4 then layer = "poi_detail" end
	obj:LayerAsCentroid(layer)
	SetNameAttributes(obj)
	obj:AttributeNumeric("rank", rank)
	obj:Attribute("class", class)
	obj:Attribute("subclass", subclass)
end

-- Set name attributes on any object
function SetNameAttributes(obj)
	local name = obj:Find("name"), iname
	local main_written = name
	-- if we have a preferred language, then write that (if available), and additionally write the base name tag
	if preferred_language and obj:Holds("name:" .. preferred_language) then
		iname = obj:Find("name:" .. preferred_language)
		obj:Attribute(preferred_language_attribute, iname)
		if iname ~= name and default_language_attribute then
			obj:Attribute(default_language_attribute, name)
		else
			main_written = iname
		end
	else
		obj:Attribute(preferred_language_attribute, name)
	end
	-- then set any additional languages
	for i, lang in ipairs(additional_languages) do
		iname = obj:Find("name:" .. lang)
		if iname == "" then iname = name end
		if iname ~= main_written then obj:Attribute("name:" .. lang, iname) end
	end
end

-- Set ele and ele_ft on any object
function SetEleAttributes(obj)
	local ele = obj:Find("ele")
	if ele ~= "" then
		local meter = math.floor(tonumber(ele) or 0)
		local feet = math.floor(meter * 3.2808399)
		obj:AttributeNumeric("ele", meter)
		obj:AttributeNumeric("ele_ft", feet)
	end
end

function SetBrunnelAttributes(obj)
	if obj:Find("bridge") == "yes" then
		obj:Attribute("brunnel", "bridge")
	elseif obj:Find("tunnel") == "yes" then
		obj:Attribute("brunnel", "tunnel")
	elseif obj:Find("ford") == "yes" then
		obj:Attribute("brunnel", "ford")
	end
end

-- Set minimum zoom level by area
function SetMinZoomByArea(way)
	local area = way:Area()
	if area > ZRES5 ^ 2 then
		way:MinZoom(6)
	elseif area > ZRES6 ^ 2 then
		way:MinZoom(7)
	elseif area > ZRES7 ^ 2 then
		way:MinZoom(8)
	elseif area > ZRES8 ^ 2 then
		way:MinZoom(9)
	elseif area > ZRES9 ^ 2 then
		way:MinZoom(10)
	elseif area > ZRES10 ^ 2 then
		way:MinZoom(11)
	elseif area > ZRES11 ^ 2 then
		way:MinZoom(12)
	elseif area > ZRES12 ^ 2 then
		way:MinZoom(13)
	else
		way:MinZoom(14)
	end
end

-- Calculate POIs (typically rank 1-4 go to 'poi' z12-14, rank 5+ to 'poi_detail' z14)
-- returns rank, class, subclass
function GetPOIRank(obj)
	local k, list, v, class, rank

	-- Can we find the tag?
	for k, list in pairs(poiTags) do
		if list[obj:Find(k)] then
			v     = obj:Find(k) -- k/v are the OSM tag pair
			class = poiClasses[v] or k
			rank  = poiClassRanks[class] or 25
			return rank, class, v
		end
	end

	-- Catch-all for shops
	local shop = obj:Find("shop")
	if shop ~= "" then return poiClassRanks['shop'], "shop", shop end

	-- Nothing found
	return nil, nil, nil
end

function SetBuildingHeightAttributes(way)
	local height = tonumber(way:Find("height"), 10)
	local minHeight = tonumber(way:Find("min_height"), 10)
	local levels = tonumber(way:Find("building:levels"), 10)
	local minLevel = tonumber(way:Find("building:min_level"), 10)

	local renderHeight = BUILDING_FLOOR_HEIGHT
	if height or levels then
		renderHeight = height or (levels * BUILDING_FLOOR_HEIGHT)
	end
	local renderMinHeight = 0
	if minHeight or minLevel then
		renderMinHeight = minHeight or (minLevel * BUILDING_FLOOR_HEIGHT)
	end

	-- Fix upside-down buildings
	if renderHeight < renderMinHeight then
		renderHeight = renderHeight + renderMinHeight
	end

	way:AttributeNumeric("render_height", renderHeight)
	way:AttributeNumeric("render_min_height", renderMinHeight)
end

-- Implement z_order as calculated by Imposm
-- See https://imposm.org/docs/imposm3/latest/mapping.html#wayzorder for details.
function SetZOrder(way)
	local highway = way:Find("highway")
	local layer = tonumber(way:Find("layer"))
	local bridge = way:Find("bridge")
	local tunnel = way:Find("tunnel")
	local zOrder = 0
	if bridge ~= "" and bridge ~= "no" then
		zOrder = zOrder + 10
	elseif tunnel ~= "" and tunnel ~= "no" then
		zOrder = zOrder - 10
	end
	if not (layer == nil) then
		if layer > 7 then
			layer = 7
		elseif layer < -7 then
			layer = -7
		end
		zOrder = zOrder + layer * 10
	end
	local hwClass = 0
	-- See https://github.com/omniscale/imposm3/blob/53bb80726ca9456e4a0857b38803f9ccfe8e33fd/mapping/columns.go#L251
	if highway == "motorway" then
		hwClass = 9
	elseif highway == "trunk" then
		hwClass = 8
	elseif highway == "primary" then
		hwClass = 6
	elseif highway == "secondary" then
		hwClass = 5
	elseif highway == "tertiary" then
		hwClass = 4
	else
		hwClass = 3
	end
	zOrder = zOrder + hwClass
	way:ZOrder(zOrder)
end

-- ==========================================================
-- Lua utility functions

function split(inputstr, sep) -- https://stackoverflow.com/a/7615129/4288232
	if sep == nil then
		sep = "%s"
	end
	local t = {};
	i = 1
	for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
		t[i] = str
		i = i + 1
	end
	return t
end

-- vim: tabstop=2 shiftwidth=2 noexpandtab