Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

// request.cf · coarse context

A page that knows where it met you.

Only coarse request metadata is shown. This demo does not display or persist visitor IP addresses.

Country
US
Cloudflare location
CMH
Connection
HTTP/2
Language
Not provided

Ray ID: a406576a7befbe76

Jump to content

Module:ObliqueDanishAirPhoto

From Wikipedia, the free encyclopedia

local p = {}

-- Ellipsoid constants (WGS84 / ETRS89)
local a = 6378137.0
local f = 1 / 298.257223563
local b = a * (1 - f)
local e2 = (a^2 - b^2) / a^2
local e_prime2 = (a^2 - b^2) / b^2
local k0 = 0.9996
local FALSE_EASTING = 500000.0

local function rad(deg) return deg * math.pi / 180 end

--- Forward Transverse Mercator: (Lat, Lon, Central Meridian) -> (Easting, Northing)
local function latLonToUtm(latDeg, lonDeg, centralMeridianDeg)
    local phi = rad(latDeg)
    local lambda = rad(lonDeg)
    local lambda0 = rad(centralMeridianDeg)

    local sin_phi = math.sin(phi)
    local cos_phi = math.cos(phi)
    local tan_phi = math.tan(phi)

    local N = a / math.sqrt(1 - e2 * sin_phi^2)
    local T = tan_phi^2
    local C = e_prime2 * cos_phi^2
    local A = (lambda - lambda0) * cos_phi

    local M = a * (
        (1 - e2/4 - 3*e2^2/64 - 5*e2^3/256) * phi
        - (3*e2/8 + 3*e2^2/32 + 45*e2^3/1024) * math.sin(2*phi)
        + (15*e2^2/256 + 45*e2^3/1024) * math.sin(4*phi)
        - (35*e2^3/3072) * math.sin(6*phi)
    )

    local easting = FALSE_EASTING + k0 * N * (
        A 
        + ((1 - T + C) * (A^3 / 6))
        + ((5 - 18*T + T^2 + 72*C - 58*e_prime2) * (A^5 / 120))
    )

    local northing = k0 * (
        M + N * tan_phi * (
            (A^2 / 2)
            + ((5 - T + 9*C + 4*C^2) * (A^4 / 24))
            + ((61 - 58*T + T^2 + 600*C - 330*e_prime2) * (A^6 / 720))
        )
    )

    return easting, northing
end

--- Geographic extent this link is meaningful for.
-- skraafoto only serves Danish imagery, so anything outside Denmark would
-- produce a valid-looking but useless link. The eastern bound includes
-- Bornholm (~15.2E), which EPSG:25832 covers even though it lies in zone 33.
local MIN_LAT, MAX_LAT = 54.4, 57.9
local MIN_LON, MAX_LON = 7.8, 15.3

local EARTH_ITEM = "Q2"

local function errorSpan(msg)
    return '<span class="error">Error: ' .. msg .. '</span>'
end

--- Is this coordinate value on Earth? A missing globe means Earth by default.
local function isOnEarth(val)
    if not val.globe then
        return true
    end
    return string.match(val.globe, "(Q%d+)$") == EARTH_ITEM
end

--- Get Lat/Lon from Wikidata (best-ranked P625 statement on current page)
local function getWikidataCoordinates()
    -- Wikibase client is not enabled on every wiki.
    if not mw.wikibase then
        return nil, nil
    end

    local entityId = mw.wikibase.getEntityIdForCurrentPage()
    if not entityId then
        return nil, nil
    end

    -- getBestStatements honours statement rank and, unlike getEntity, does not
    -- load the whole item (which counts against the expensive parser limit).
    local statements = mw.wikibase.getBestStatements(entityId, "P625")
    for _, statement in ipairs(statements or {}) do
        local snak = statement.mainsnak
        if snak and snak.datavalue and snak.datavalue.value then
            local val = snak.datavalue.value
            if val.latitude and val.longitude and isOnEarth(val) then
                return val.latitude, val.longitude
            end
        end
    end

    return nil, nil
end

--- Entry point for template/invoke
function p.zone32NFromPage(frame)
    local args = frame.args
    if not args[1] and not args.lat then
        -- getParent returns nil when #invoke is called from a page directly
        -- rather than through a template wrapper.
        local parent = frame:getParent()
        args = parent and parent.args or args
    end

    local lat = tonumber(args.lat or args[1])
    local lon = tonumber(args.lon or args[2])

    -- Fetch from Wikidata if no explicit parameters are passed
    if not lat or not lon then
        lat, lon = getWikidataCoordinates()
    end

    if not lat or not lon then
        return errorSpan("No coordinates found on page or Wikidata.")
    end

    if lat < MIN_LAT or lat > MAX_LAT or lon < MIN_LON or lon > MAX_LON then
        return errorSpan(string.format(
            "Coordinates (%.5f, %.5f) are outside the covered area.", lat, lon))
    end

    -- Convert Geographic (Lat/Lon) to UTM Zone 32N (Central Meridian = 9°E)
    local e32, n32 = latLonToUtm(lat, lon, 9.0)

    -- Format link: https://skraafoto.dataforsyningen.dk/?center=easting,northing
    local url = string.format("https://skraafoto.dataforsyningen.dk/?center=%.2f,%.2f", e32, n32)

    return string.format("[%s Oblique air photography]", url)
end

return p