Module:ObliqueDanishAirPhoto
Appearance
| This module is rated as ready for general use. It has reached a mature state, is considered relatively stable and bug-free, and may be used wherever appropriate. It can be mentioned on help pages and other Wikipedia resources as an option for new users. To minimise server load and avoid disruptive output, improvements should be developed through sandbox testing rather than repeated trial-and-error editing. |
Usage
[edit]This module uses the Wikidata property:
{{#invoke:ObliqueDanishAirPhoto|zone32NFromPage}}
The above snippet yields a single link with the label "Oblique air photography". You can for example use it in the "External Links" section of a page that has coordinates.
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