Module:Cyclone map

require('strict') local fn = require('Module:Formatnum') --local mm = require('Module:Math') local Date = require('Module:Date')._Date local p = {}

-- N/A but possible option: keep cyclone data in subpage data files --local stormDatabase = require( "Module:Cyclone map/data" ) -- configuration module -- main function callable in Wikipedia via the #invoke command. p.main = function(frame) local str = p.getMapframeString return frame:preprocess(str)  -- the mapframe needs to be preprocessed!!!!! end -- End the function.

-- function to construct mapframe string      sets up the tags                 MAPDATA in form of geojson constucted with function getGeoJSON -- p.getMapframeString = function(frame)

--get mapframe arguments from calling templates local parent = mw.getCurrentFrame:getParent -- get JSON data for features to display local mapData = p.getGeoJSON local mapString = ""

--mapString = '' if mapData ~= "" then

mapString = '<mapframe' if parent.args['frameless'] then -- don't and text as this overrides frameless parameter mapString = mapString .. ' frameless' else mapString = mapString .. ' text="' .. (parent.args['text'] or "") .. '"' end -- set width and height using noth parameters, one parameter assuming 4:3 aspect ratio, or defaults local aspect = 4/3 local width = parent.args['width']                                 --or "220" local height = parent.args['height'] or (width or 220)/aspect    --or "165" width = width or height*aspect                                  -- if width null, use height local align = parent.args['align'] or "right"

mapString = mapString .. ' width='    .. math.floor(width) .. ' height='   .. math.floor(height) .. ' align='    .. align

local zoom     = parent.args['zoom'] --or "0"          -- no longer set defaults (mapframe does automatically) local latitude = parent.args['latitude'] --or "0" local longitude = parent.args['longitude'] --or "0" --set if values, otherwise allow mapframe to set automatically (TODO check if longitude and latitude are independent) if zoom     then  mapString = mapString .. ' zoom='     .. zoom     end if latitude then  mapString = mapString .. ' latitude=' .. latitude end if longitude then mapString = mapString .. ' longitude=' .. longitude end mapString = mapString .. ' >' .. mapData   .. ' '  -- add data and close tag else mapString = "No data for map" end return mapString

end -- End the function.

--imageN=, |descriptionN=)     (2) from values in the data module (i.e. Module:Football map/data) [not function for cyclone map]      (3) from Wikidata p.getGeoJSON = function(frame)

-- now we need to iterate through the stadiumN parameters and get data for the feature markers local maxNumber = 200 -- maximum number looked for local mapData = "" local cycloneName = nil local cycloneID = nil --get mapframe arguments from calling templates local parent = mw.getCurrentFrame:getParent --There are three ways of getting data about the stadium features       	(1) from a list in the module subpages (n/a but possible alternative)        	(2) from wikidata         	(3) from the parameters in the template (these always override other)    	The parameters useWikiData, useModule restrict use of source    -- local useWikidata = true local useModule = false if parent.args['wikidata'] then useWikidata = true; useModule = false end -- use wikidata or template data (no module data) if parent.args['moduledata'] then useModule = true; useWikidata = false end -- use module of template data (no wikidata) if parent.args['templatedata'] then useModule = false; useWikidata = false end -- only use template data -- default parameters for marker color, size and symbol, etc (i.e. those without index suffix) local strokeColor = parent.args['stroke'] or "#000000" if strokeColor == "auto" then strokeColor = nil end -- if using auto color -- the default properties are set by the unindexed parameters and affect all objects local defaultProperties = { ['marker-color']  = parent.args['marker-color'], -- default to nil --or  "#0050d0", ['marker-size']   = parent.args['marker-size'] or "small", ['marker-symbol'] = parent.args['marker-symbol'] or "circle", ['stroke']        = strokeColor,  --parent.args['stroke'] or "#000000",          -- nil default causes autocolor path; a value overrides autocolor ['stroke-width']  = parent.args['stroke-width'] or 1, ['stroke-opacity'] = parent.args['stroke-opacity'] or 1.0, -- these are for shapes drawn with polygon instead of the marker symbol ['symbol-stroke'] = parent.args['symbol-stroke'],   -- nil default causes autocolor path; a value overrides autocolor ['symbol-fill']   = parent.args['symbol-fill'],     -- nil default causes autocolor path; ['symbol-shape']   = parent.args['symbol-shape'] or "circle", ['symbol-size']   = parent.args['symbol-size'] or 0.4, ['symbol-stroke-width']  = parent.args['symbol-stroke-width'] or 0, ['symbol-stroke-opacity'] = parent.args['symbol-stroke-opacity'] or 1.0, ['symbol-fill-opacity']  = parent.args['symbol-fill-opacity'] or 1.0 }

local index=0 while index < maxNumber do    	index = index + 1 local cycloneID = "" -- (1) get cyclone name cycloneID  = parent.args['id'..tostring(index)] cycloneName = parent.args['name'..tostring(index)] if cycloneName and not cycloneID then cycloneID = mw.wikibase.getEntityIdForTitle(cycloneName) end if cycloneID and not cycloneName then cycloneName = mw.wikibase.getLabel( cycloneID ) --TODO get associated Wikipedia page for linking end -- if we have a valid cyclone id (note:Lua has no continue statement) if cycloneID then local feature = {name="",alias="",latitude=0,longitude=0,description="",image="",valid=false, path={} } local validFeatureData = true -- assume now -- (2) get feature parameters from module (n/a) or wikidata or both --if useModule then	-- get feature parameters from module data stadium list	          feature = p.getModuleData(frame, stadiumName)	        end if useWikidata and cycloneID then --and feature['name'] == "" then -- get feature parameters from wikidata feature = p.getDataFromWikiData(cycloneName,cycloneID) if not feature['valid'] then -- no valid coordinates validFeatureData =false mw.addWarning( "No valid coordinates found for " .. cycloneName .. " (" .. cycloneID .. ")" )	       	end end -- (3) data from template parameters will override those obtainied from a module table or wikidata local templateArgs = { latitude   = parent.args['latitude'..tostring(index)], longitude  = parent.args['longitude'..tostring(index)], description = parent.args['description'..tostring(index)], image      = parent.args['image'..tostring(index)] }		   if templateArgs['latitude'] and templateArgs['longitude']  then -- if both explicitly set by template feature['latitude'] = templateArgs['latitude'] feature['longitude']= templateArgs['longitude'] feature['name'] = cycloneName -- as we have valid coordinates validFeatureData =true end -- use specified description and image if provided if templateArgs['description'] then feature['description'] = templateArgs['description'] end if templateArgs['image'] then feature['image'] = templateArgs['image']   -- priority for image from template argument end if feature['image'] ~= "" then feature['image'] =  .. feature['image'] ..  end -- wikilink - use redirect if alias if feature['alias'] ~= '' then feature['name'] = .. feature['alias'] ..  else feature['name'] =  .. feature['name'] ..  end

if feature['image'] ~= "" then feature['description'] = feature['image'] .. feature['description'] end

--check if current feature marker has specified color, size or symbol local strokeColor = parent.args['stroke'..tostring(index)] or defaultProperties['stroke'] if strokeColor == "auto" then strokeColor = nil end -- if using auto color

-- the feature properties are set by the indexed parameters or defaults (see above) local featureProperties = { ['marker-color']  = parent.args['marker-color'..tostring(index)]   or defaultProperties['marker-color'], ['marker-symbol'] = parent.args['marker-symbol'..tostring(index)]  or defaultProperties['marker-symbol'], ['marker-size']   = parent.args['marker-size'..tostring(index)]    or defaultProperties['marker-size'], ['stroke']        = strokeColor, --parent.args['stroke'..tostring(index)]         or defaultProperties['stroke'], ['stroke-width']  = parent.args['stroke-width'..tostring(index)]   or defaultProperties['stroke-width'], ['stroke-opacity'] = parent.args['stroke-opacity'..tostring(index)] or defaultProperties['stroke-opacity'],

-- these are for shapes drawn with polygon instead of the marker symbol ['symbol-stroke']        = parent.args['symbol-stroke'..tostring(index)]         or defaultProperties['symbol-stroke'],          -- nil default causes autocolor path; a value overrides autocolor ['symbol-fill']          = parent.args['symbol-fill'..tostring(index)]           or defaultProperties['symbol-fill'], ['symbol-shape']         = parent.args['symbol-shape'..tostring(index)]          or defaultProperties['symbol-shape'], ['symbol-size']          = parent.args['symbol-size'..tostring(index)]           or defaultProperties['symbol-size'], ['symbol-stroke-width']  = parent.args['symbol-stroke-width'..tostring(index)]   or defaultProperties['symbol-stroke-width'], ['symbol-stroke-opacity'] = parent.args['symbol-stroke-opacity'..tostring(index)] or defaultProperties['symbol-stroke-opacity'], ['symbol-fill-opacity']  = parent.args['symbol-fill-opacity'..tostring(index)]   or defaultProperties['symbol-fill-opacity'] }	       --(4) construct the json for the features (if we have a storm with valid coordinates) if validFeatureData then local featureData = ""

if feature.path[1] then                                                -- add path if multiple coordinates featureData = p.addPathFeatureCollection(feature,featureProperties) else                                                                   -- else show single marker -- make sure a marker color is set (if not set by template or not autocoloring storm path) -- note the default colour is left as nil for the auto coloring of paths by storm type --   and that this can be overriden with a value, but might be nil here local markerColor = featureProperties['marker-color'] or "#0050d0" featureData = '{ "type": "Feature", ' .. ' "geometry": { "type": "Point", "coordinates": [' .. feature['longitude'] .. ',' 		   	                             .. feature['latitude'] .. '] }, ' 		   	            .. ' "properties": { "title": "'      .. feature['name']  .. '", ' .. '"description": "'  .. feature['description'] ..'", ' .. '"marker-symbol": "' .. featureProperties['marker-symbol'] .. '", ' .. '"marker-size": "'  .. featureProperties['marker-size'] .. '", ' .. '"marker-color": "' .. markerColor .. '"  } ' .. ' } '		       end if index > 1 and mapData ~= "" then mapData = mapData .. ',' .. featureData else mapData = featureData end else --mapData = '{ "type": "Feature",  "geometry": { "type": "Point", "coordinates": [-0.066417, 51.60475] }, "properties": { "title": "White Hart Lane (default)",  "description": "Stadium of Tottenham Hotspur F.C.", "marker-symbol": "soccer", "marker-size": "large",  "marker-color": "0050d0"   }  } ' mw.addWarning( "No valid information found for " .. cycloneName .. " (" .. cycloneID .. ")" )			end -- if valid parameters end -- end if if cycloneID end -- end while loop -- (5) check for external data (geoshape) 	       TODO add more than index=1 and generalise for any json feature	 -- local geoshape = parent.args['geoshape'..tostring(1)] or "" if geoshape ~= "" then mapData = mapData .. ',' .. geoshape -- assumes at least one stadium end -- add outer bracket to json if more than one element if index > 1 then mapData = '[' .. mapData .. ']'	 	--mapData = ' {"type": "FeatureCollection", "features": [' .. mapData .. ']}' -- is there an advantage using this? end return mapData end -- End the function.

--[[ functions adding path to cyclone item 		p.addPathFeatureCollection   -- adds markers/symbols and lines for path of storm (cordinates from wikidata)		p.addShapeFeature             -- returns geoJson for the custom symbol		p.getPolygonCoordinates       -- returns coordinate set for the custom symbol (loop for diffent shapes)		p.calculatePolygonCoordinates -- calculates the coordinates for the specified shape		p.getCycloneColor             -- sets color of symbols/lines based on storm type (from wikidata)

--]] p.addPathFeatureCollection=function(feature, featureProperties) if not feature.path[1] then return "" end -- shouldn't be necessary now local mode = mw.getCurrentFrame:getParent.args['mode']  or "default"

local featureCollection = "" local sep = "" local i = 1 table.sort (feature.path, function(a,b)				                 if (a.timeStamp < b.timeStamp) then                --- primary sort on timeStamp		                             return true		                          else            		                        	 return false		                          end		                      end) for i, v in pairs(feature.path) do local autoColor = p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties ) local markerColor = featureProperties['marker-color'] or autoColor local strokeColor = featureProperties['stroke'] or autoColor local longitude = feature.path[i]['longitude'] local latitude = feature.path[i]['latitude']

-- add a lines between the points (current point to the next point, if there is one) local lineFeature = "" if feature.path[i+1] then local longitude2 = feature.path[i+1]['longitude'] local latitude2 = feature.path[i+1]['latitude']

lineFeature =   '{ "type": "Feature", ' .. ' "geometry": { "type": "LineString", "coordinates": [' .. '[' .. longitude .. ',' .. latitude .. '],'		   	                           .. '[' .. longitude2 .. ',' .. latitude2 .. ']'		   	                           .. '] }, ' 		    	      .. ' "properties": {  "stroke": "' .. strokeColor .. '", ' .. ' "stroke-width": ' .. featureProperties['stroke-width'] .. ', ' 		   	                       .. ' "stroke-opacity": ' .. featureProperties['stroke-opacity'] .. ' } ' 		    	          .. ' } '					featureCollection = featureCollection .. sep .. lineFeature sep = "," end

-- place mapframe markers and custom symbols on each object		      mode="marker": use mapframe markers for mark storm objects		       mode="test": use marker on first point of path and linePoint symbol		       default: use custom polygons to mark storm objects if mode == "marker" or (mode == "test" and i==1) then

local pointFeature =          '{ "type": "Feature", ' .. ' "geometry": { "type": "Point", "coordinates": [' .. longitude .. ',' .. latitude  .. '] }, ' 		   	            .. ' "properties": { "title": "'  .. feature['name']  .. '", ' .. '"description": "' .. feature['description'] .. ' Type: ' .. feature.path[i]['cycloneType']  .. '", ' .. '"marker-symbol": "' .. featureProperties['marker-symbol'] .. '", ' .. '"marker-size": "' .. featureProperties['marker-size'] .. '", ' .. '"marker-color": "' .. markerColor .. '" } ' .. ' } '					featureCollection = featureCollection .. sep .. pointFeature sep = ","

elseif mode == "test" then --  short lines (test) to mark with objects local dateString = " 2020-06" if feature.path[i]['timeStamp'] then local formattedDate = Date(feature.path[i]['timeStamp']):text("dmy hm") dateString = ' date and time: ' .. formattedDate --tostring( feature.path[i]['timeStamp'] ) end local description = ' latitude: ' .. tostring(latitude) .. ' longitude: ' .. tostring(longitude) .. dateString .. ' '			local circleFeature =  '{ "type": "Feature", ' .. ' "geometry": { "type": "LineString", "coordinates": [' .. '[' .. longitude .. ',' .. latitude .. '],'		   	                           .. '[' .. (longitude) .. ',' .. (latitude) .. ']'		   	                           .. '] }, ' 		    	      .. ' "properties": {  "stroke": "' .. markerColor .. '", ' .. ' "stroke-width": 10, ' -- TODO change size based on marker size .. ' "description": "' .. description .. '"' .. ' } ' 		    	          .. ' } '					featureCollection = featureCollection .. sep .. circleFeature sep = "," else -- use polygons (default if not marker)  to mark with objects

featureCollection = featureCollection .. sep .. p.addShapeFeature(i, feature, featureProperties) sep = "," end i=i+1   -- increment for next point in storm path end -- while/for in pairs if mw.getCurrentFrame:getParent.args['mode'] == "test3" then featureCollection = '{"type": "FeatureCollection", "features": [ {"type": "Feature","properties": {"marker-color": "#bbccff", "marker-symbol": "-number"}, "geometry": { "type": "Point", "coordinates": [80.298888888889,6.3316666666667]}}, {"type": "Feature","properties": {"marker-color": "#bbccff", "marker-symbol": "-number"}, "geometry": { "type": "Point", "coordinates": [80.263888888889,6.6644444444444]}}, {"type": "Feature","properties": {"marker-color": "#bbccff", "marker-symbol": "-number"}, "geometry": { "type": "Point", "coordinates": [80.434444444444,7.1883333333333]}}, {"type": "Feature","properties": {"marker-color": "#bbccff", "marker-symbol": "-number"}, "geometry": { "type": "Point", "coordinates": [80.656111111111,8.1086111111111]}}, {"type": "Feature","properties": {"marker-color": "#bbccff", "marker-symbol": "-number"}, "geometry": { "type": "Point", "coordinates": [80.9025,8.2961111111111]}}, {"type":"Feature", "properties": { "stroke":"#D3D3D3", "stroke-opacity":0.7, "stroke-width":50}, "geometry": {"type":"LineString", "coordinates": 80.298888888889,6.3316666666667],[80.263888888889,6.6644444444444],[80.434444444444,7.1883333333333],[80.656111111111,8.1086111111111],[80.9025,8.2961111111111}} ]}' end --return sep .. featureCollection return  featureCollection end -- function p.addShapeFeature(i, feature, featureProperties)		function adding shape features using polygon type: square, triangle, stars, etc p.addShapeFeature =function(i, feature, featureProperties) local size = featureProperties['symbol-size'] local shape = featureProperties['symbol-shape']                     -- symbol for tropical cyclone if feature.path[i]['cycloneType2'] == 'extratropical cyclone' then shape = 'triangle'                                              -- symbol for extratropical cyclone (Q1063457) elseif feature.path[i]['cycloneType2'] == 'subtropical cyclone' then shape = 'square'                                                -- symbol for subtropical cyclone (Q2331851) end local autoColor = p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties ) --local markerColor = featureProperties['symbol-color'] or autoColor local strokeColor = featureProperties['symbol-stroke'] or autoColor local fillColor = featureProperties['symbol-fill'] or autoColor local longitude = feature.path[i]['longitude'] local latitude = feature.path[i]['latitude']

local description = ' ' --    .. ' date: ' .. mw.language.getContentLanguage:formatDate('d F Y', feature.path[i]['timeStamp']) .. ' date: ' .. Date(feature.path[i]['timeStamp']):text("dmy")   -- :text("dmy hm") .. ' type: ' .. tostring(feature.path[i]['cycloneType']) .. ' longitude: ' .. fn.formatNum(longitude,"en",6) .. ' latitude: ' .. fn.formatNum(latitude,"en",6) .. ' '	local shapeFeature ="" --shape="circle" shapeFeature = ' { "type": "Feature", ' .. ' "geometry": {  "type": "Polygon", ' .. ' "coordinates": [ ' .. p.getPolygonCoordinates(shape, size, latitude, longitude) ..' ] ' .. ' }, ' 	   	       .. ' "properties": { "stroke": "' .. strokeColor .. '", ' .. ' "fill": "' .. fillColor .. '" ,' .. ' "fill-opacity": 1, ' .. ' "stroke-width": ' .. featureProperties['symbol-stroke-width'] .. ','	   	                       .. ' "description": "' .. description .. '"' .. ' } ' 	    	          .. ' } '		    -- if shape==cyclone, a circle shape will have been drawn; now add the tails if shape=="cyclone" then		 -- superimpose a second shape local shape2="cyclone_tails" shapeFeature = shapeFeature  .. ', '		          .. '{ "type": "Feature", ' .. ' "geometry": {  "type": "Polygon", ' .. ' "coordinates": [ ' .. p.getPolygonCoordinates(shape2, size, latitude, longitude) ..' ] ' .. ' }, ' 	   	      .. ' "properties": {  "stroke": "' .. strokeColor .. '", ' .. ' "fill": "' .. fillColor .. '" ,' .. ' "fill-opacity": 1, ' .. ' "stroke-width": ' .. 0 .. ','	   	                       .. ' "description": "' .. description .. '"' .. ' } ' 	    	          .. ' } '			end return shapeFeature end p.getPolygonCoordinates = function(shape, size, latitude, longitude) -- shape = "circle" -- shape ="spiral" local coordinates = "" if shape == "square" then coordinates = '	[ ' .. '[' .. (longitude+size) .. ',' .. (latitude+size) .. '],'	   	                           .. '[' .. (longitude+size) .. ',' .. (latitude-size) .. '],'	   	                           .. '[' .. (longitude-size) .. ',' .. (latitude-size) .. '],'	   	                           .. '[' .. (longitude-size) .. ',' .. (latitude+size) .. '],'	   	                           .. '[' .. (longitude+size) .. ',' .. (latitude+size) .. ']'	   	                           .. '] '	elseif shape == "triangle2"  then coordinates = '	[ ' .. '[' .. (longitude) .. ',' .. (latitude+size) .. '],'	   	                           .. '[' .. (longitude+size) .. ',' .. (latitude-size) .. '],'	   	                           .. '[' .. (longitude-size) .. ',' .. (latitude-size) .. '],'	   	                           .. '[' .. (longitude) .. ',' .. (latitude+size) .. ']'	   	                           .. '] '	elseif shape == "inverse-triangle"  then coordinates = '	[ ' .. '[' .. (longitude) .. ',' .. (latitude-size) .. '],'	   	                           .. '[' .. (longitude+size) .. ',' .. (latitude+size) .. '],'	   	                           .. '[' .. (longitude-size) .. ',' .. (latitude+size) .. '],'	   	                           .. '[' .. (longitude) .. ',' .. (latitude-size) .. ']'	   	                           .. '] '	elseif shape == "star2"  then --size = size * 5 coordinates = ' [ ' .. '[' .. longitude           .. ',' .. latitude+(size*1.2) .. '],'    -- top point .. '[' .. longitude+(size*0.2) .. ',' .. latitude+(size*0.2) .. '],'                      .. '[' .. longitude+(size*1.2) .. ',' .. latitude+(size*0.4) .. '],'    -- 2pm point .. '[' .. longitude+(size*0.3) .. ',' .. latitude-(size*0.1) .. '],'                      .. '[' .. longitude+(size)     .. ',' .. latitude-(size)     .. '],'    -- 5pm point .. '[' .. longitude           .. ',' .. latitude-(size*0.3) .. '],'    -- 6pm (innner) .. '[' .. longitude-(size)    .. ',' .. latitude-(size)     .. '],'    -- 7pm point .. '[' .. longitude-(size*0.3) .. ',' .. latitude-(size*0.1) .. '],'                      .. '[' .. longitude-(size*1.2) .. ',' .. latitude+(size*0.4) .. '],'    -- 10pm point .. '[' .. longitude-(size*0.2) .. ',' .. latitude+(size*0.2) .. '],'                      .. '[' .. longitude            .. ',' .. latitude+(size*1.2) .. ']'     -- top point (close) .. '] '

elseif shape == "circle2" then local radius = size coordinates = coordinates  .. ' [ '		for angle = 0, 360, 3 do if angle > 0 then coordinates = coordinates .. ',' end coordinates = coordinates .. '[' .. longitude +(radius*math.cos(math.rad(angle)))   .. ','         	                                  .. latitude  +(radius*math.sin(math.rad(angle))) .. ']'            end coordinates = coordinates  .. '] ' 	elseif shape == "cyclone_tails" then

local radius = size*2 -- add tail at 3 o'clock coordinates = coordinates  .. ' [ '		for angle = 0, 60, 3 do if angle > 0 then coordinates = coordinates .. ',' end coordinates = coordinates .. '[' .. longitude-size+(radius*math.cos(math.rad(angle)))   .. ','         	                                  .. latitude +(radius*math.sin(math.rad(angle))) .. ']'            end coordinates = coordinates  .. '] '

-- add tail at 9 o'clock coordinates = coordinates  .. ', [ '		for angle = 180, 240, 3 do if angle > 180 then coordinates = coordinates .. ',' end coordinates = coordinates .. '[' .. longitude+size+(radius*math.cos(math.rad(angle)))   .. ','         	                                  .. latitude +(radius*math.sin(math.rad(angle))) .. ']'            end coordinates = coordinates  .. '] ' 		-- add tail at 6 o'clock coordinates = coordinates  .. ', [ '		for angle = 270, 330, 3 do if angle > 270 then coordinates = coordinates .. ',' end coordinates = coordinates .. '[' .. longitude+(radius*math.cos(math.rad(angle)))   .. ','         	                                  .. latitude+size +(radius*math.sin(math.rad(angle))) .. ']'            end coordinates = coordinates  .. '] '

-- add tail at 6 o'clock coordinates = coordinates  .. ', [ '		for angle = 90, 150, 3 do if angle > 90 then coordinates = coordinates .. ',' end coordinates = coordinates .. '[' .. longitude+(radius*math.cos(math.rad(angle)))   .. ','         	                                  .. latitude-size +(radius*math.sin(math.rad(angle))) .. ']'            end coordinates = coordinates  .. '] '        -- for adding circle        local  radius = size		coordinates = coordinates   .. ', [ '		for angle = 0, 360, 3 do        	if angle > 0 then coordinates = coordinates .. ',' end        	coordinates = coordinates  .. '[' .. longitude+(radius*math.cos(math.rad(angle)))    .. ','          	                                  .. latitude +(radius*math.sin(math.rad(angle)))         	                                  .. ']'             end        coordinates = coordinates   .. '] '         -- elseif shape == "spiral" then

coordinates = ' [ ' local radius = size*0.01 for angle = 0, 360*4, 4 do			radius = radius + size*0.01 if angle > 0 then coordinates = coordinates .. ',' end coordinates = coordinates .. '[' .. longitude+(radius*math.cos(math.rad(angle)))   .. ','         	                                  .. latitude +(radius*math.sin(math.rad(angle))) .. ']'            end coordinates = coordinates  .. '] ' 	elseif shape == "circle" or shape == "cyclone" then                         -- circle as 120 sided polygon return p.calculatePolygonCoordinates(120, 1, size, latitude, longitude) elseif shape == "triangle" then return p.calculatePolygonCoordinates(3, 1, size, latitude, longitude) elseif shape == "diamond" then return p.calculatePolygonCoordinates(4, 1, size, latitude, longitude) elseif shape == "hexagon" then return p.calculatePolygonCoordinates(6, 1, size, latitude, longitude) elseif shape == "octagon" then return p.calculatePolygonCoordinates(8, 1, size, latitude, longitude) elseif shape == "star4" then return p.calculatePolygonCoordinates(8, 3, size, latitude, longitude) elseif shape == "star5" then return p.calculatePolygonCoordinates(10, 3, size, latitude, longitude) elseif shape == "star8" then return p.calculatePolygonCoordinates(16, 3, size, latitude, longitude) elseif shape == "star12" then return p.calculatePolygonCoordinates(24, 3, size, latitude, longitude) elseif shape == "star" then return p.calculatePolygonCoordinates(10, 2, size, latitude, longitude) end return coordinates end --  p.calculatePolygonCoordinates(sides, ratio, size, latitude, longitude)               calculates coordinates for polygons or stars            a star is a polygon with alternate points with different radii (determined by ratio)            sides: number of sides on polygon                   for a star this is twice the number of points of the star            ratio: ratio of inner and outer radii for a star (a higher number makes a more pointy star)                   use 1 for a simple polygon             size:  the outer radius of the a circle surrounding the polygon            latitude and longitude: self explanatory p.calculatePolygonCoordinates = function(sides, ratio, size, latitude, longitude) local coordinates = ' [ '

local outer = true local radius = size for angle = 0, 360, 360/sides do if angle > 0 then coordinates = coordinates .. ',' end  -- important for geojson structure (unlike Lua) if radius ~= 1 then                                              -- if a star if outer then radius = size else radius = size/ratio end     -- alternate inner and outer radius outer = not outer end coordinates = coordinates .. '[' .. longitude+(radius*math.sin(math.rad(angle)))   .. ','         	                                  .. latitude +(radius*math.cos(math.rad(angle))) .. ']'            end return coordinates   .. '] ' 	end --    p.getCycloneColor=function(cycloneType, featureProperties)    sets color of symbols/lines based on storm type (from wikidata) p.getCycloneColor=function(cycloneType, featureProperties) -- codors from "Tropical cyclone scales" article local color = "#000000" cycloneType = string.lower(cycloneType) if cycloneType == "depression" or cycloneType == "zone of disturbed weather" or cycloneType == "Tropical disturbance" then color = "#80ccff" elseif cycloneType == "tropical depression" or cycloneType == "deep depression" or cycloneType == "tropical Depression" or cycloneType == "tropical low" then color = "#5ebaff" elseif cycloneType == "cyclonic storm" or cycloneType == "tropical storm" or cycloneType == "moderate tropical storm" or cycloneType == "category 1 tropical cyclone" then color = "#00faf4" elseif cycloneType == "severe cyclonic storm" or cycloneType == "severe tropical storm" or cycloneType == "category 1 hurricane" then color = "#ccffff" elseif cycloneType == "very severe cyclonic storm" or cycloneType == "tropical cyclone" then color = "#ffffcc" elseif cycloneType == "typhoon" then color = "#fdaf9a" elseif cycloneType == "very strong typhoon" or cycloneType == "extremely severe cyclonic storm" or cycloneType == "intense tropical cyclone" or cycloneType == "category 4 severe tropical storm" then color = "#ffc140" elseif cycloneType == "violent typhoon"    or cycloneType == "category 5 severe tropical cyclone" or cycloneType == "super typhoon" or cycloneType == "super cyclonic storm" or cycloneType == "very intense tropical cyclone" or cycloneType == "category 5 major hurricane" then color = "#ff6060" end return color end

--[[---Retrieve information from wikidata-	statements of interest (datavalue element)		item = mw.wikibase.getEntity(WikidataId), 		statements = item:getBestStatements('P625')[1]   	"claims":			P625 coordinate location (value.longitude/latitude)			   "P625":[{ "mainsnake": { ... "datavalue": { "value": {"latitude": 51.4, "longitude": -0.19] ...			   statements.mainsnak.datavalue.value.latitude			P18 image on commons (value, "File:value")		   	   "P18":[{ "mainsnake": { ... "datavalue": { "value": "Stamford Bridge Clear Skies.JPG"			P1566 GeoNames ID (value, "geonames.org/value")			P31 (instance of) Q483110 (stadium)			   "P18":[{ "mainsnake": { ... "datavalue": { "value": { "id": "Q483110"			   however also sports venue, olympic stadium, association football stadium			P159 headquarters location (for football club) 			   e..g. London			   qualifier property: coordinates(P625)    page title on enwiki    	mw.wikibase.getSitelink( itemId ) - gets local version    	"sitelink": { "enwiki": { "title": "Hurricane Katrina" }    other properties of possible interest:    	P276 location		P17 country		P580 start time		P582 end time		P1120 number of deaths		P2630 cost of damage		P2532 lowest atmospheric pressure		P2895 maximum sustained winds

--]] p.getDataFromWikiData=function(cycloneName,cycloneID) local wd={name="",latitude="",longitude="",description="",image="",alias="",type="",valid=false, path = {} } -- 	get wikidata id corresponding to wikipedia stadium page --local WikidataId = mw.wikibase.getEntityIdForTitle(cycloneName) local WikidataId = cycloneID if not cycloneName then cycloneName = "unnamed" end --TODO get the name if WikidataId and mw.wikibase.isValidEntityId( WikidataId ) then -- valid id   	local item = mw.wikibase.getEntity(WikidataId) if not item then return wd end -- will test for wiki

local enwikiTitle =	mw.wikibase.getSitelink( WikidataId ) -- name of local Wikipedia page local wikidataTitle = mw.wikibase.getLabel( WikidataId ) -- name of Wikidata page if enwikiTitle and wikidataTitle and enwikiTitle ~= wikidataTitle then wd['alias'] = wikidataTitle wd['name'] =cycloneName else wd['name'] =cycloneName end -- get storm type P31 instance of    	local statements = item:getBestStatements('P31') --coordinate location if statements and statements[2] then -- check cordinates available local type = statements[2].mainsnak.datavalue.value.id or "" wd['type'] = mw.wikibase.getLabel( type ) end -- get coordinates local statements = item:getBestStatements('P625')[1] --coordinate location if statements ~= nil then -- check cordinates available local coord = statements.mainsnak.datavalue.value if type(coord.latitude) == 'number' and type(coord.longitude) == 'number' then -- add coordinate data from wikidata for unindexed stadium wd['latitude'] = coord.latitude wd['longitude'] = coord.longitude wd['valid'] = true -- if we have a path of coordinates if item:getBestStatements('P625')[2] then -- TODO make sure ordinal number local i = 1 while item:getBestStatements('P625')[i]  do			        -- get coordinates local statements = item:getBestStatements('P625')[i] --coordinate location if statements ~= nil then -- check cordinates available local coord = statements.mainsnak.datavalue.value if type(coord.latitude) == 'number' and type(coord.longitude) == 'number' then -- add coordinate data from wikidata for path wd.path[i] = {} wd.path[i].latitude = coord.latitude wd.path[i].longitude = coord.longitude -- get series ordinal as index (now removed so set to i) -- TODO sort based on point in time, i.e. wd.path(i).timeStamp wd.path[i].index = i -- statements.qualifiers['P1545'][1]['datavalue']['value']   -- P1545 = series ordinal {a number] -- get storm type using instance of (P31) local cycloneType = statements.qualifiers['P31'][1]['datavalue']['value']['id'] -- P31 = instance of [cyclone type] if cycloneType then wd.path[i].cycloneType = mw.wikibase.getLabel( cycloneType ) end -- get storm type using instance of (P31) if statements.qualifiers['P31'][2] then cycloneType = statements.qualifiers['P31'][2]['datavalue']['value']['id'] -- P31 = instance of [cyclone type] if cycloneType then wd.path[i].cycloneType2 = mw.wikibase.getLabel( cycloneType ) end end --get point in time (P585) qualifier local timeStamp = statements.qualifiers['P585'][1]['datavalue']['value']['time'] if timeStamp then wd.path[i].timeStamp = timeStamp end end end i=i+1 end -- end while loop end end end -- end if coordinate statements --get image statements = item:getBestStatements('P18')[1] --image if statements ~= nil then wd['image'] = 'File:' .. statements.mainsnak.datavalue.value end

end

return wd

end

----	This function gets data from a module subpage (not implemented) p.getModuleData = function (frame, stormName) local feature = {} feature['name'] = "" --feature['data'] = "" feature['alias'] = "" feature['description'] = "" feature['image'] = "" -- check the module storm list for name match -- set feature parameters from the module data for _, params in pairs( stormDatabase.storm ) do		   	if stormName == params[1] then -- if we have a match from the list feature['name'] = params[1] feature['latitude'] = params[2] feature['longitude'] = params[3] feature['alias'] = params[4] feature['description'] = params[5] feature['image'] = params[6] break end end return feature end

-- All modules end by returning the variable containing its functions to Wikipedia. return p