ניתן ליצור תיעוד על היחידה הזאת בדף יחידה:מפהמפורטת/תיעוד
-- Credits: -- Original from Wikivoyage -- Developed for Kartographer version on Wikipedia by Vriullop @cawiki -- Formulae: -- CSGNetwork at http://www.csgnetwork.com/degreelenllavcalc.html via @enwiki -- OpenStreetMap -- Version: 20170813 local p = {} -- Localization on [[Module:Map/i18n]] local i18n = { ["coordinate-invalid"] = "פרמטר $1 הוא ערך בלתי תקין של \"רוחב,אורך\".", ["type-invalid"] = "סוג $1 אינו תקין. השתמשו ב mapframe או ב-maplink.", ["geotype-invalid"] = "Geotype $1 הוא ערך בלתי תקין.", ["ids-invalid"] = "פרמטר מזהים $1 אינו תקין.", ["polygon-required-points"] = "פוליגון דורש מינימום של 4 נקודות ציון.", ["polygon-not-closed"] = "פוליגון סגור דורש שהנקודה הראשונה תהיה זהה לנקודה האחרונה.", ['ids-not-found'] = "מזהים לא נמצאו עבור מידע חיצוני.", ['not-from-content-page'] = "Do not invoke from content page. Use a template or use a module subpage like /sandbox for testing .", -- local categories ['cat-several-features'] = "", ['cat-linestring-drawn'] = "", ['cat-polygon-drawn'] = "", } local cat = {['cat-several-features'] = false, ['cat-linestring-drawn'] = false, ['cat-polygon-drawn'] = false} -- Credit to http://stackoverflow.com/a/1283608/2644759, cc-by-sa 3.0 local function tableMerge(t1, t2) for k, v in pairs(t2) do if type(v) == "table" then if type(t1[k] or false) == "table" then tableMerge(t1[k] or {}, t2[k] or {}) else t1[k] = v end else t1[k] = v end end return t1 end local function loadI18n() local exist, res = pcall(require, "Module:מפה/i18n") if exist and next(res) ~= nil then tableMerge(i18n, res.i18n) end end loadI18n() local errormessage local function printError(key, par) -- just print first error errormessage = errormessage or ('<span class="error">' .. (par and mw.ustring.gsub(i18n[key], "$1", par) or i18n[key]) .. '</span>') end -- Convert coordinates input format to geojson table local function parseGeoSequence(data, geotype) local coordsGeo = {} for line_coord in mw.text.gsplit(data, ':', true) do -- Polygon - linearRing:linearRing... local coordsLine = {} for point_coord in mw.text.gsplit(line_coord, ';', true) do -- LineString or MultiPoint - point;point... local valid = false local val = mw.text.split(point_coord, ',', true) -- Point - lat,lon -- allow for elevation if #val >= 2 and #val <= 3 then local lat = tonumber(val[1]) local lon = tonumber(val[2]) if lat ~= nil and lon ~= nil then table.insert(coordsLine, {lon, lat}) valid = true end end if not valid and point_coord ~= '' then printError('coordinate-invalid', point_coord) end end if geotype == 'Polygon' then if #coordsLine < 4 then printError('polygon-required-points') elseif table.concat(coordsLine[1]) ~= table.concat(coordsLine[#coordsLine]) then printError('polygon-not-closed') end end table.insert(coordsGeo, coordsLine) end if geotype == 'Point' then coordsGeo = coordsGeo[1][1] elseif geotype == "LineString" or geotype == "MultiPoint" then coordsGeo = coordsGeo[1] elseif geotype ~= 'Polygon' then printError('geotype-invalid', geotype) end return coordsGeo end -- data Point - {lon,lat} -- data LineString - { {lon,lat}, {lon,lat}, ... } -- data Polygon - { { {lon,lat}, {lon,lat} }, { {lon,lat}, {lon,lat} }, ... } -- output as LineString format local function mergePoints(stack, merger) if merger == nil then return stack end for _, val in ipairs(merger) do if type(val) == "number" then -- Point format stack[#stack + 1] = merger break elseif type(val[1]) == "table" then -- Polygon format for _, val2 in ipairs(val) do stack[#stack + 1] = val2 end else -- LineString format stack[#stack + 1] = val end end return stack end local function getCoordBounds(data) local latN, latS = -90, 90 local lonE, lonW = -180, 180 for i, val in ipairs(data) do latN = math.max(val[2], latN) latS = math.min(val[2], latS) lonE = math.max(val[1], lonE) lonW = math.min(val[1], lonW) end return latN, latS, lonE, lonW end local function getCoordCenter(data) local latN, latS, lonE, lonW = getCoordBounds(data) local latCenter = latS + (latN - latS) / 2 local lonCenter = lonW + (lonE - lonW) / 2 return lonCenter, latCenter end -- meters per degree by latitude local function mxdByLat(lat) local latRad = math.rad(lat) -- see [[Geographic coordinate system#Expressing latitude and longitude as linear units]], by CSGNetwork local mxdLat = 111132.92 - 559.82 * math.cos(2 * latRad) + 1.175 * math.cos(4 * latRad) - 0.023 * math.cos(6 * latRad) local mxdLon = 111412.84 * math.cos(latRad) - 93.5 * math.cos(3 * latRad) + 0.118 * math.cos(5 * latRad) return mxdLat, mxdLon end -- Calculate zoom to fit coordinate bounds into height and width of frame local function getZoom(data, height, width) local lat1, lat2, lon1, lon2 = getCoordBounds(data) local latMid = (lat1 + lat2) / 2 -- mid latitude local mxdLat, mxdLon = mxdByLat(latMid) -- distances in meters local distLat = math.abs((lat1 - lat2) * mxdLat) local distLon = math.abs((lon1 - lon2) * mxdLon) -- margin 100px in height and width, right upper icon is about 50x50px local validHeight = math.max(height - 100, 100) local validWidth = math.max(width - 100, 100) -- maximum zoom fitting all points local latRad = math.rad(latMid) for zoom = 18, 0, -1 do -- see https://wiki.openstreetmap.org/wiki/Zoom_levels#Metres_per_pixel_math -- equatorial circumference 40 075 036 m: [[Equator#Exact length]] local distLatFrame = 40075036 * validHeight * math.cos(latRad) / (2 ^ (zoom + 8)) local distLonFrame = 40075036 * validWidth * math.cos(latRad) / (2 ^ (zoom + 8)) if distLatFrame > distLat and distLonFrame > distLon then return zoom end end return 0 end -- Geotype based on coordinates format pattern local function findGeotype(coord) local _, semicolons = string.gsub(coord, ';', '') local firstcoord = string.match(coord, "[0-9%.%-]+%s*,%s*[0-9%.%-]+") local lastcoord = string.match(string.reverse(coord), "[0-9%.%-]+%s*,%s*[0-9%.%-]+") if firstcoord == nil or lastcoord == nil then printError('coordinate-invalid', coord) else lastcoord = string.reverse(lastcoord) end if string.find(coord, ':') or (semicolons > 2 and firstcoord == lastcoord) then return 'Polygon' elseif semicolons > 0 then return 'LineString' -- or MultiPoint else return 'Point' end end local function fetchWikidata(id, snak) -- snak is a table like {'claims', 'P625', 1, 'mainsnak', 'datavalue', 'value'} -- see function ViewSomething on Module:Wikidata local value id = mw.text.trim(id) if not string.find(id, "^Q%d+$") then printError('ids-invalid', id) else value = {['claims']= {} } value.claims[snak[2]] = mw.wikibase.getBestStatements(id, snak[2]) for i in ipairs(snak) do if value == nil then break end value = value[snak[i]] end end return value end -- Fetch coordinates from Wikidata for a list of comma separated ids local function getCoordinatesById(ids) if ids == nil then return end local coord = {} local snak = {'claims', 'P625', 1, 'mainsnak', 'datavalue', 'value'} for idx in mw.text.gsplit(ids, '%s*,%s*') do local value = fetchWikidata(idx, snak) if value then coord[#coord+1] = value.latitude .. ',' .. value.longitude end end return #coord > 0 and table.concat(coord, ';') or nil end local function getBoundsById(ids, coordInput) if ids == nil then return {} end local coord = mw.text.split(coordInput, '%s*;%s*') local id = mw.text.split(ids, '%s*,%s*') if #coord ~= #id then return {} end local bounds = {} -- try to fetch Wikidata in this order: area, watershed area, population local snak_area = {'claims', 'P2046', 1, 'mainsnak', 'datavalue', 'value'} -- area and unit local snak_warea = {'claims', 'P2053', 1, 'mainsnak', 'datavalue', 'value'} -- area and unit local snak_pop = {'claims', 'P1082', 1, 'mainsnak', 'datavalue', 'value'} -- population local convert_area = {['Q712226'] = 1000000, ['Q35852'] = 10000, ['Q232291'] = 2589988.110336, ['Q81292'] = 4046.8564224, ['Q935614'] = 1600, ['Q857027'] = 0.09290304, ['Q21074767'] = 1138100, ['Q25343'] = 1} -- to square metres -- query Wikidata: http://tinyurl.com/j8aez2g for i = 1, #id do local amount, unit, area local value = fetchWikidata(id[i], snak_area) or fetchWikidata(id[i], snak_warea) if value then amount = tonumber(value.amount) unit = string.match(value.unit, "(Q%d+)") if convert_area[unit] then area = amount * convert_area[unit] end end if area == nil then value = fetchWikidata(id[i], snak_pop) if value then amount = tonumber(value.amount) -- average density estimated for populated areas: 100; see [[Population density]] area = amount / 100 * 1000000 end end if area then local radius = math.sqrt(area / math.pi) -- approximation with a circle local latlon = mw.text.split(coord[i], '%s*,%s*') local mxdLat, mxdLon = mxdByLat(latlon[1]) bounds[#bounds+1] = {latlon[2] + (radius / mxdLon), latlon[1] + (radius / mxdLat)} -- NE bound, geoJSON format bounds[#bounds+1] = {latlon[2] - (radius / mxdLon), latlon[1] - (radius / mxdLat)} -- SW bound end end return bounds end local function addCategories(geotype, i) if not mw.title.getCurrentTitle().isContentPage then return end if i > 2 and i18n["cat-several-features"] ~= '' then cat["cat-several-features"] = true end if geotype == "LineString" and i18n["cat-linestring-drawn"] ~= '' then cat["cat-linestring-drawn"] = true elseif geotype == "Polygon" and i18n["cat-polygon-drawn"] ~= '' then cat["cat-polygon-drawn"] = true end return end -- Main function function p._tag(args) local tagname = args.type or 'mapframe' if tagname ~= 'maplink' and tagname ~= 'mapframe' then printError('type-invalid', tagname) end local geojson local tagArgs = { text = args['טקסט'], zoom = tonumber(args['זום']), latitude = tonumber(args['רוחב']), longitude = tonumber(args['אורך']) } local defaultzoom = tonumber(args['זום ברירת מחדל']) if tagname == 'mapframe' then tagArgs.width = args['רוחב מפה'] or 300 tagArgs.height = args['אורך מפה'] or 300 tagArgs.align = args['יישור'] or 'left' if args['ללא מסגרת'] ~= nil and tagArgs.text == nil then tagArgs.frameless = true end else tagArgs.class = args.class end if args['קואורדינטות1'] == nil and args['geotype1'] == nil then -- single feature args['קואורדינטות1'] = args['קואורדינטות'] or args[1] if args['קואורדינטות1'] == nil and args['רוחב'] and args['אורך'] then args['קואורדינטות1'] = args['רוחב'] .. ',' .. args['אורך'] elseif args['קואורדינטות1'] == nil then args['קואורדינטות1'] = getCoordinatesById(mw.wikibase.getEntityIdForCurrentPage()) end args['שם1'] = args['שם1'] or args['שם'] args['תמונה1'] = args['תמונה1'] or args['תמונה'] args['תיאור1'] = args['תיאור1'] or args['תיאור'] args['geotype1'] = args['geotype1'] or args['geotype'] end local externalData = {['geoshape'] = true, ['geomask'] = true, ['geoline'] = true} local featureCollection = {['Point'] = true, ['MultiPoint'] = true, ['LineString'] = true, ['Polygon'] = true} local myfeatures, myexternal, allpoints = {}, {}, {} local i, j = 1, 1 while args['קואורדינטות'..i] or externalData[args['geotype'..i]] do local geotypex = args['geotype'..i] or args['geotype'] if geotypex ~= nil and not (featureCollection[geotypex] or externalData[geotypex]) then printError('geotype-invalid', geotypex) break end local mystack if externalData[geotypex or ''] then mystack = myexternal j = #mystack + 1 mystack[j] = {} mystack[j]['type'] = "ExternalData" mystack[j]['service'] = geotypex mystack[j]['ids'] = args['פריטים'..i] or args['פריטים'] or mw.wikibase.getEntityIdForCurrentPage() if mystack[j]['ids'] == nil then printError('ids-not-found'); break end local mycoordinates = args['קואורדינטות'..i] if mycoordinates == nil and (tagArgs.latitude == nil or tagArgs.longitude == nil or tagArgs.zoom == nil) then mycoordinates = getCoordinatesById(mystack[j]['ids']) end if mycoordinates ~= nil then local mypoints = getBoundsById(mystack[j]['ids'], mycoordinates) if #mypoints == 0 then mypoints = parseGeoSequence(mycoordinates, mycoordinates:find(';') and 'MultiPoint' or 'Point') end allpoints = mergePoints(allpoints, mypoints) end else mystack = myfeatures j = #mystack + 1 mystack[j] = {} mystack[j]['type'] = "Feature" mystack[j]['geometry'] = {} mystack[j]['geometry']['type'] = geotypex or findGeotype(args['קואורדינטות'..i]) mystack[j]['geometry']['coordinates'] = parseGeoSequence(args['קואורדינטות'..i], mystack[j]['geometry']['type']) allpoints = mergePoints(allpoints, mystack[j]['geometry']['coordinates']) addCategories(mystack[j]['geometry']['type'], i) end mystack[j]['properties'] = {} mystack[j]['properties']['title'] = args['שם'..i] or (geotypex and geotypex .. i) or mystack[j]['geometry']['type'] .. i if args['תמונה'..i] then args['תיאור'..i] = (args['תיאור'..i] or '') .. '[[File:' .. args['תמונה'..i] .. '|300px]]' end mystack[j]['properties']['description'] = args['תיאור'..i] mystack[j]['properties']['marker-size'] = args['גודל סמן'..i] or args['גודל סמן'] mystack[j]['properties']['marker-symbol'] = args['סמל סמן'..i] or args['סמל סמן'] mystack[j]['properties']['marker-color'] = args['צבע סמן'..i] or args['צבע סמן'] mystack[j]['properties']['stroke'] = args['צבע קו מתאר'..i] or args['צבע קו מתאר'] mystack[j]['properties']['stroke-opacity'] = tonumber(args['אטימות קו מתאר'..i] or args['אטימות קו מתאר']) mystack[j]['properties']['stroke-width'] = tonumber(args['עובי קו מתאר'..i] or args['עובי קו מתאר']) mystack[j]['properties']['fill'] = args['מילוי'..i] or args['מילוי'] mystack[j]['properties']['fill-opacity'] = tonumber(args['אטימות מילוי'..i] or args['אטימות מילוי']) i = i + 1 end -- calculate defaults for static mapframe; maplink is dynamic if (tagArgs.latitude == nil or tagArgs.longitude == nil) and #allpoints > 0 then if tagname == "mapframe" or tagArgs.text == nil then -- coordinates needed for text in maplink tagArgs.longitude, tagArgs.latitude = getCoordCenter(allpoints) end end if tagArgs.zoom == nil then if tagname == "mapframe" then if #allpoints == 1 then local currentId = mw.wikibase.getEntityIdForCurrentPage() local coordInput = allpoints[1][2] .. ',' .. allpoints[1][1] local mybounds = getBoundsById(currentId, coordInput) -- try to fetch by area allpoints = mergePoints(allpoints, mybounds) end if #allpoints <= 1 then tagArgs.zoom = defaultzoom or 9 else tagArgs.zoom = getZoom(allpoints, tagArgs.height, tagArgs.width) end else tagArgs.zoom = defaultzoom end end local geojson = myexternal if #myfeatures > 0 then geojson[#geojson + 1] = {type = "FeatureCollection", features = myfeatures} end if args.debug ~= nil then local html = mw.text.tag{name = tagname, attrs = tagArgs, content = mw.text.jsonEncode(geojson, mw.text.JSON_PRETTY)} return 'syntaxhighlight', tostring(html) .. ' Arguments:' .. mw.text.jsonEncode(args, mw.text.JSON_PRETTY), {lang = 'json'} end return tagname, geojson and mw.text.jsonEncode(geojson) or '', tagArgs end function p.tag(frame) if mw.title.new(frame:getParent():getTitle()).isContentPage and not mw.title.new(frame:getTitle()).isSubpage then -- invoked from a content page and not invoking a module subpage printError('not-from-content-page') end local getArgs = require('Module:Arguments').getArgs local args = getArgs(frame) local tag, geojson, tagArgs = p._tag(args) local categories = '' if errormessage then categories = mw.message.new('Kartographer-broken-category'):inLanguage(mw.language.getContentLanguage().code):plain() return errormessage .. '[[Category:' .. categories .. ']]' end return frame:extensionTag(tag, geojson, tagArgs) .. categories end return p