Jump to content

Module:Cyclone map: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
change handling on mapframe attributes to allow automatic setting of zoom, longitude and latitude
change test2 to default
Line 342: Line 342:
end
end
end)
end)
for i, v in pairs(feature.path) do
for i, v in pairs(feature.path) do
--while feature.path[i] do
--while feature.path[i] do
Line 371: Line 372:


-- place marker symbol on each point
-- place marker symbol on each point
if mode == "default" or (mode == "test" and i==1) then
if mode == "marker" or (mode == "test" and i==1) then




Line 387: Line 388:
-- use short lines or polygons to mark with circle
-- use short lines or polygons to mark with circle
if mode == "test2" then
if mode == "test" then
local dateString = " 2020-06"
if feature.path[i]['timeStamp'] then
local formattedDate = Date(feature.path[i]['timeStamp']):text("dmy hm")
dateString = '<br/>date and time: ' .. formattedDate --tostring( feature.path[i]['timeStamp'] )
end
local description = '<div>latitude: ' .. tostring(latitude)
.. '<br/>longitude: ' .. tostring(longitude)
.. dateString
.. '</div>'
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 = ","
elseif mode ~= "marker" then -- former "test2" option
--[[
--[[
local size = 0.3
local size = 0.3
Line 412: Line 437:
featureCollection = featureCollection .. sep .. p.addShapeFeature(i, feature, featureProperties)
featureCollection = featureCollection .. sep .. p.addShapeFeature(i, feature, featureProperties)
sep = ","
sep = ","
elseif mode == "test" then
local dateString = " 2020-06"
if feature.path[i]['timeStamp'] then
local formattedDate = Date(feature.path[i]['timeStamp']):text("dmy hm")
dateString = '<br/>date and time: ' .. formattedDate --tostring( feature.path[i]['timeStamp'] )
end
local description = '<div>latitude: ' .. tostring(latitude)
.. '<br/>longitude: ' .. tostring(longitude)
.. dateString
.. '</div>'
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 = ","
end
end

Revision as of 09:08, 27 June 2020

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

local stadiumDatabase = require( "Module:Football 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 <mapframe> tags
       <mapframe width= height= latitude=  longitude= zoom= >
          MAPDATA in form of geojson constucted with function getGeoJSON()
       </mapframe>
--]]
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 = '<mapframe text="London football stadia" width=800 height=650 align=left zoom=11 latitude=51.530 longitude=-0.16 >'
    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 width = parent.args['width'] --or "220"
	    local height = parent.args['height'] or (width or "220")*3/4 --or "165"
	    width = width or height*4/3                                             -- if width null, use height
	    
	    local align = parent.args['align'] or "right"
	    local zoom      = parent.args['zoom'] --or "0" 
		local latitude  = parent.args['latitude'] --or "0"
		local longitude = parent.args['longitude'] --or "0"
	    mapString = mapString  
	                      .. ' width='     .. width
	                      .. ' height='    .. height
	                      .. ' align='     .. align
	    
	    --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   .. '</mapframe>'
    else
    	mapString = "No data for map"
    end
    
    return mapString    

end  -- End the function.

--[[ function getGeoJSON() - to construct JSON format data for markers on map.
     The information for each marker (coordinate, description and image for popup, etc) 
     can be set in several ways (in order of priority):
      (1) using arguments in the template (|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 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']         = parent.args['stroke'],          -- nil default causes autocolor path; a value overrides autocolor
                            ['stroke-width']   = parent.args['stroke-width'] or 3,
                            ['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 "star",
                            ['symbol-size']    = parent.args['symbol-size'] or 0.4,
                            ['symbol-stroke-width']   = parent.args['symbol-stroke-width'] or 1,
                            ['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['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 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']         = 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 
		function p.getCycloneColor() - sets color of symbols/lines based on storm type (from wikidata)
		function p.addPathFeatureCollection - adds symbols/lines for path of storm (cordinates from wikidata)
--]]
p.getCycloneColor=function(cycloneType, featureProperties)
	
	--[[ codors from "Tropical cyclone scales" article
          	#80ccff		Depression, Zone of Disturbed Weather, Tropical Disturbance (?) 	
          	#5ebaff		Tropical Depression, Deep Depression 
          	            Tropical Disturbance (?), Tropical Depression, Tropical Low
          	#00faf4 	tropical storm, moderate tropical storm, cyclonic storm, Category 1 Tropical Cyclone
          	#ccffff 	severe cyclonic storm, severe tropical storm, Category 1 Hurricane 
          	#ffffcc		Very Severe Cyclonic Storm 	Tropical Cyclone 
	        #fdaf9a		Typhoon
	        #ffc140		Very Strong Typhoon, Extremely Severe Cyclonic Storm, Intense Tropical Cyclone 	Category 4
			#ff6060 	Violent Typhoon, Category 5 Severe Tropical Cyclone, Super Typhoon, 
						Super Cyclonic Storm, Very Intense Tropical Cyclone, Category 5 Major Hurricane 
	
	]]
	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
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
	--while feature.path[i] do
		local markerColor = featureProperties['marker-color'] or p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties )
		local strokeColor = featureProperties['stroke'] or p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties )
		
		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 marker symbol on each point
		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'] .. '<br>Type: ' .. feature.path[i]['cycloneType']   .. '", ' 
		    	                          .. '"marker-symbol": "' .. featureProperties['marker-symbol'] .. '", '
		    	                          .. '"marker-size": "' .. featureProperties['marker-size'] .. '", ' 
		    	                          .. '"marker-color": "' .. markerColor .. '"  } ' 
		    	            .. ' } '		
			featureCollection = featureCollection .. sep .. pointFeature
			sep = ","
		end
		
		-- use short lines or polygons to mark with circle
		if mode == "test"  then
			
			local dateString = " 2020-06"
			if feature.path[i]['timeStamp'] then 
				local formattedDate = Date(feature.path[i]['timeStamp']):text("dmy hm") 
				dateString = '<br/>date and time: ' .. formattedDate  --tostring( feature.path[i]['timeStamp'] )
			end
			local description = '<div>latitude: ' .. tostring(latitude)  
			               ..  '<br/>longitude: ' .. tostring(longitude) 
			               .. dateString
			               .. '</div>'
			
			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 = ","
		elseif mode ~= "marker" then  -- former  "test2"  option
			--[[
			local size = 0.3
			local description = '<div>latitude: ' .. tostring(latitude)  
			               ..  '<br/>longitude: ' .. tostring(longitude) .. '</div>'
			
			local shapeFeature  =  '{ "type": "Feature", ' 
		    	       .. ' "geometry": { "type": "Polygon", "coordinates": [ ['
		    	                           .. '[' .. (longitude+size) .. ','  .. (latitude+size) .. '],'
		    	                           .. '[' .. (longitude+size) .. ','  .. (latitude-size) .. '],'
		    	                           .. '[' .. (longitude-size) .. ','  .. (latitude-size) .. '],'
		    	                           .. '[' .. (longitude-size) .. ','  .. (latitude+size) .. '],'
		    	                           .. '[' .. (longitude+size) .. ','  .. (latitude+size) .. ']'
		    	                           .. '] ] }, ' 
		    	      .. ' "properties": {  "stroke": "' .. strokeColor .. '" , ' 
		    	                       .. ' "fill": "' .. strokeColor .. '" ,' 
		    	                       .. ' "fill-opacity": 1, ' 
		    	                       .. ' "stroke-width": 1, ' -- .. featureProperties['stroke-width'] 
		    	                       .. ' "description": "' .. description .. '"' 
		    	                          .. '  } ' 
		    	          .. ' } '		
			featureCollection = featureCollection .. sep .. shapeFeature
			--]]
			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']
    if feature.path[i]['cycloneType2'] == 'extratropical cyclone' then  --extratropical cyclone (Q1063457)  subtropical cyclone (Q2331851) 
    	shape = 'triangle'
    elseif feature.path[i]['cycloneType2'] == 'subtropical cyclone' then
    	shape = 'square'
    end
	--local markerColor = featureProperties['symbol-color'] or p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties )
	local strokeColor = featureProperties['symbol-stroke'] or '#000088'  p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties )
	local fillColor = featureProperties['symbol-fill'] or p.getCycloneColor( feature.path[i]['cycloneType'], featureProperties )
	

	local longitude = feature.path[i]['longitude']
	local latitude = feature.path[i]['latitude'] 

	
	local description = '<div style=\\"text-align:left;\\"> '  
			        
			   --     .. '<br/>date: ' .. mw.language.getContentLanguage():formatDate('d F Y', feature.path[i]['timeStamp'])  
			        .. '<br/>date: ' .. Date(feature.path[i]['timeStamp']):text("dmy")    -- :text("dmy hm") 
			        .. '<br/>type: ' .. tostring(feature.path[i]['cycloneType'])
			        .. '<br/>longitude: ' .. fn.formatNum(longitude,"en",6) 
			        .. '<br/>latitude: '  .. fn.formatNum(latitude,"en",6)  
			        .. '</div>'
	
	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" and mw.getCurrentFrame():getParent().args['mode'] == "test2" then		 -- superimpose a second shape
		shape="cyclone_tails"
		shapeFeature  = shapeFeature  .. ', '
		           .. '{ "type": "Feature", ' 
	    	       .. ' "geometry":  {  "type": "Polygon", '
	    	                       .. ' "coordinates": [ ' .. p.getPolygonCoordinates(shape, 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

--[[-------------------------------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