def getFrontPage(config): """ Returns a front page for the WMS, containing example links """ doc = StringIO() doc.write("<html><head><title>%s</title></head>" % config.title) doc.write("<body><h1>%s</h1>" % config.title) doc.write("<p><a href=\"" + prefix + "?SERVICE=WMS&REQUEST=GetCapabilities\">Capabilities document</a></p>") doc.write("<p><a href=\"./godiva2.html\">Godiva2 interface</a></p>") doc.write("<h2>Datasets:</h2>") # Print a GetMap link for every dataset we have doc.write("<table border=\"1\"><tbody>") doc.write("<tr><th>Dataset</th>") for format in getmap.getSupportedImageFormats(): doc.write("<th>%s</th>" % format) if config.allowFeatureInfo: doc.write("<th>FeatureInfo</th>") doc.write("</tr>") datasets = config.datasets for ds in datasets.keys(): doc.write("<tr><th>%s</th>" % datasets[ds].title) vars = datareader.getVariableMetadata(datasets[ds].location) for format in getmap.getSupportedImageFormats(): doc.write("<td>") for varID in vars.keys(): doc.write("<a href=\"WMS.py?SERVICE=WMS&REQUEST=GetMap&VERSION=1.3.0&STYLES=&CRS=CRS:84&WIDTH=256&HEIGHT=256&FORMAT=%s" % format) doc.write("&LAYERS=%s%s%s" % (ds, wmsUtils.getLayerSeparator(), varID)) bbox = vars[varID].bbox doc.write("&BBOX=%s,%s,%s,%s" % tuple([str(b) for b in bbox])) if vars[varID].tvalues is not None: doc.write("&TIME=%s" % iso8601.tostring(vars[varID].tvalues[-1])) doc.write("\">%s</a><br />" % vars[varID].title) doc.write("</td>") if config.allowFeatureInfo: doc.write("<td>") if datasets[ds].queryable: for varID in vars.keys(): doc.write("<a href=\"WMS.py?SERVICE=WMS&REQUEST=GetFeatureInfo&VERSION=1.3.0&CRS=CRS:84&WIDTH=256&HEIGHT=256&INFO_FORMAT=text/xml") doc.write("&QUERY_LAYERS=%s%s%s" % (ds, wmsUtils.getLayerSeparator(), varID)) bbox = vars[varID].bbox doc.write("&BBOX=%s,%s,%s,%s" % tuple([str(b) for b in bbox])) doc.write("&I=128&J=128") if vars[varID].tvalues is not None: doc.write("&TIME=%s" % iso8601.tostring(vars[varID].tvalues[-1])) doc.write("\">%s</a><br />" % vars[varID].title) else: doc.write("Dataset not queryable") doc.write("</td>") doc.write("</tr>") doc.write("</tbody></table>") doc.write("</body></html>") s = doc.getvalue() doc.close() return s
def getVariables(config, dataset): """ returns an HTML table containing a set of variables for the given dataset. """ str = StringIO() str.write("<table cellspacing=\"0\"><tbody>") datasets = config.datasets vars = datareader.getVariableMetadata(datasets[dataset].location) for varID in vars.keys(): str.write("<tr><td>") str.write("<a href=\"#\" onclick=\"javascript:variableSelected('%s', '%s')\">%s</a>" % (dataset, varID, vars[varID].title)) str.write("</td></tr>") str.write("</tbody></table>") s = str.getvalue() str.close() return s
def getVariableDetails(config, dataset, varID): """ returns an XML document containing the details of the given variable in the given dataset. """ str = StringIO() datasets = config.datasets var = datareader.getVariableMetadata(datasets[dataset].location)[varID] str.write("<variableDetails dataset=\"%s\" variable=\"%s\" units=\"%s\">" % (dataset, var.title, var.units)) str.write("<axes>") if var.zvalues is not None: str.write("<axis type=\"z\" units=\"%s\" positive=\"%d\">" % (var.zunits, var.zpositive)) for z in var.zvalues: str.write("<value>%f</value>" % math.fabs(z)) str.write("</axis>") str.write("</axes>") str.write("<range><min>%f</min><max>%f</max></range>" % (var.validMin, var.validMax)) str.write("</variableDetails>") s = str.getvalue() str.close() return s
def getTimesteps(config, dataset, varID, tIndex): """ Returns an HTML select box allowing the user to select a set of times for a given day """ datasets = config.datasets # Get an array of time axis values in seconds since the epoch tValues = datareader.getVariableMetadata(datasets[dataset].location)[varID].tvalues # TODO: is this the right thing to do here? if tValues is None: return "" str = StringIO() # We find a list of indices of timesteps that are on the same day # as the provided tIndex indices = {} reftime = time.gmtime(tValues[tIndex]) indices[tIndex] = "%02d:%02d:%02d" % (reftime[3], reftime[4], reftime[5]) for i in xrange(tIndex + 1, len(tValues)): t = time.gmtime(tValues[i]) diff = _compareDays(reftime, t) if diff == 0: indices[i] = "%02d:%02d:%02d" % (t[3], t[4], t[5]) else: break for i in xrange(tIndex - 1, -1, -1): # count backwards through the axis indices to zero t = time.gmtime(tValues[i]) diff = _compareDays(reftime, t) if diff == 0: indices[i] = "%02d:%02d:%02d" % (t[3], t[4], t[5]) else: break # Write the selection box with the timesteps str.write("<select id=\"tValues\" onchange=\"javascript:updateMap()\">") keys = indices.keys() keys.sort() for i in keys: str.write("<option value=\"%s\">%s</option>" % (iso8601.tostring(tValues[i]), indices[i])) str.write("</select>") s = str.getvalue() str.close() return s
def getCapabilities(req, params, config, lastUpdateTime): """ Returns the Capabilities document. req = mod_python request object or WMS.FakeModPythonRequest object params = wmsUtils.RequestParser object containing the request parameters config = ConfigParser object containing configuration info for this WMS lastUpdateTime = time at which cache of data and metadata was last updated """ version = params.getParamValue("version", "") format = params.getParamValue("format", "") # TODO: deal with version and format # Check the UPDATESEQUENCE (used for cache consistency) updatesequence = params.getParamValue("updatesequence", "") if updatesequence != "": try: us = iso8601.parse(updatesequence) if round(us) == round(lastUpdateTime): # Equal to the nearest second raise CurrentUpdateSequence(updatesequence) elif us > lastUpdateTime: raise InvalidUpdateSequence(updatesequence) except ValueError: # Client didn't supply a valid ISO8601 date # According to the spec, InvalidUpdateSequence is not the # right error code here so we use a generic exception raise WMSException("UPDATESEQUENCE must be a valid ISO8601 date") output = StringIO() output.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") output.write("<WMS_Capabilities version=\"" + wmsUtils.getWMSVersion() + "\"") # UpdateSequence is accurate to the nearest second output.write(" updateSequence=\"%s\"" % iso8601.tostring(round(lastUpdateTime))) output.write(" xmlns=\"http://www.opengis.net/wms\"") output.write(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"") # The next two lines should be commented out if you wish to load this document # in Cadcorp SIS from behind the University of Reading firewall output.write(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"") output.write(" xsi:schemaLocation=\"http://www.opengis.net/wms http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd\"") output.write(">") output.write("<Service>") output.write("<Name>WMS</Name>") output.write("<Title>%s</Title>" % config.title) output.write("<Abstract>%s</Abstract>" % config.abstract) output.write("<KeywordList>") for keyword in config.keywords: output.write("<Keyword>%s</Keyword>" % keyword) output.write("</KeywordList>") output.write("<OnlineResource xlink:type=\"simple\" xlink:href=\"%s\"/>" % config.url) output.write("<ContactInformation>") output.write("<ContactPersonPrimary>") output.write("<ContactPerson>%s</ContactPerson>" % config.contactName) output.write("<ContactOrganization>%s</ContactOrganization>" % config.contactOrg) output.write("</ContactPersonPrimary>") output.write("<ContactVoiceTelephone>%s</ContactVoiceTelephone>" % config.contactTel) output.write("<ContactElectronicMailAddress>%s</ContactElectronicMailAddress>" % config.contactEmail) output.write("</ContactInformation>") output.write("<Fees>none</Fees>") output.write("<AccessConstraints>none</AccessConstraints>") output.write("<LayerLimit>%d</LayerLimit>" % getmap.getLayerLimit()) output.write("<MaxWidth>%d</MaxWidth>" % config.maxImageWidth) output.write("<MaxHeight>%d</MaxHeight>" % config.maxImageHeight) output.write("</Service>") output.write("<Capability>") output.write("<Request>") output.write("<GetCapabilities>") output.write("<Format>text/xml</Format>") url = "http://%s%s?" % (req.server.server_hostname, req.unparsed_uri.split("?")[0]) output.write("<DCPType><HTTP><Get><OnlineResource xlink:type=\"simple\" xlink:href=\"" + url + "\"/></Get></HTTP></DCPType>") output.write("</GetCapabilities>") output.write("<GetMap>") for format in getmap.getSupportedImageFormats(): output.write("<Format>%s</Format>" % format) output.write("<DCPType><HTTP><Get><OnlineResource xlink:type=\"simple\" xlink:href=\"" + url + "\"/></Get></HTTP></DCPType>") output.write("</GetMap>") if config.allowFeatureInfo: output.write("<GetFeatureInfo>") for format in getfeatureinfo.getSupportedFormats(): output.write("<Format>%s</Format>" % format) output.write("<DCPType><HTTP><Get><OnlineResource xlink:type=\"simple\" xlink:href=\"" + url + "\"/></Get></HTTP></DCPType>") output.write("</GetFeatureInfo>") output.write("</Request>") # TODO: support more exception types output.write("<Exception>") for ex_format in getmap.getSupportedExceptionFormats(): output.write("<Format>%s</Format>" % ex_format) output.write("</Exception>") # Write the top-level container layer output.write("<Layer>") output.write("<Title>%s</Title>" % config.title) # TODO: add styles for crs in grids.getSupportedCRSs().keys(): output.write("<CRS>" + crs + "</CRS>") # Now for the dataset layers datasets = config.datasets for dsid in datasets.keys(): # Write a container layer for this dataset. Container layers # do not have a Name output.write("<Layer>") output.write("<Title>%s</Title>" % datasets[dsid].title) # Now write the displayable data layers vars = datareader.getVariableMetadata(datasets[dsid].location) for vid in vars.keys(): output.write("<Layer") if config.allowFeatureInfo and datasets[dsid].queryable: output.write(" queryable=\"1\"") output.write(">") output.write("<Name>%s%s%s</Name>" % (dsid, wmsUtils.getLayerSeparator(), vid)) output.write("<Title>%s</Title>" % vars[vid].title) output.write("<Abstract>%s</Abstract>" % vars[vid].abstract) # Set the bounding box minLon, minLat, maxLon, maxLat = vars[vid].bbox output.write("<EX_GeographicBoundingBox>") output.write("<westBoundLongitude>%s</westBoundLongitude>" % str(minLon)) output.write("<eastBoundLongitude>%s</eastBoundLongitude>" % str(maxLon)) output.write("<southBoundLatitude>%s</southBoundLatitude>" % str(minLat)) output.write("<northBoundLatitude>%s</northBoundLatitude>" % str(maxLat)) output.write("</EX_GeographicBoundingBox>") output.write("<BoundingBox CRS=\"CRS:84\" ") output.write("minx=\"%f\" maxx=\"%f\" miny=\"%f\" maxy=\"%f\"/>" % (minLon, maxLon, minLat, maxLat)) # Set the level dimension if vars[vid].zvalues is not None: output.write("<Dimension name=\"elevation\" units=\"%s\"" % vars[vid].zunits) # Use the first value in the array as the default # If the default value is removed, you also need to edit # the data reading code (e.g. DataReader.java) to # disallow default z values output.write(" default=\"%s\">" % vars[vid].zvalues[0]) firstTime = 1 for z in vars[vid].zvalues: if firstTime: firstTime = 0 else: output.write(",") output.write(str(z)) output.write("</Dimension>") # Set the time dimension if vars[vid].tvalues is not None: output.write("<Dimension name=\"time\" units=\"ISO8601\">") # If we change this to support the "current" attribute # we must also change the data reading code firstTime = 1 for t in vars[vid].tvalues: if firstTime: firstTime = 0 else: output.write(",") output.write(iso8601.tostring(t)) output.write("</Dimension>") output.write("</Layer>") # end of variable Layer output.write("</Layer>") # end of dataset layer output.write("</Layer>") # end of top-level container layer output.write("</Capability>") output.write("</WMS_Capabilities>") req.content_type="text/xml" req.write(output.getvalue()) output.close() # Free the buffer return
def getCalendar(config, dataset, varID, dateTime): """ returns an HTML calendar for the given dataset and variable. dateTime is a string in ISO 8601 format with the required 'focus time' """ datasets = config.datasets # Get an array of time axis values in seconds since the epoch tValues = datareader.getVariableMetadata(datasets[dataset].location)[varID].tvalues # TODO: is this the right thing to do here? if tValues is None: return "" str = StringIO() prettyDateFormat = "%d %b %Y" # Find the closest time step to the given dateTime value # TODO: binary search would be more efficient reqTime = iso8601.parse(dateTime) # Gives seconds since the epoch diff = 1e20 for i in xrange(len(tValues)): testDiff = math.fabs(tValues[i] - reqTime) if testDiff < diff: # Axis is monotonic so we should move closer and closer # to the nearest value diff = testDiff nearestIndex = i elif i > 0: # We've moved past the closest date break str.write("<root>") str.write("<nearestValue>%s</nearestValue>" % iso8601.tostring(tValues[nearestIndex])) str.write("<prettyNearestValue>%s</prettyNearestValue>" % time.strftime(prettyDateFormat, time.gmtime(tValues[nearestIndex]))) str.write("<nearestIndex>%d</nearestIndex>" % nearestIndex) # create a struct_time tuple with zero timezone offset (i.e. GMT) nearesttime = time.gmtime(tValues[nearestIndex]) # Now print out the calendar in HTML str.write("<calendar>") str.write("<table><tbody>") # Add the navigation buttons at the top of the month view str.write("<tr>") str.write("<td><a href=\"#\" onclick=\"javascript:setCalendar('%s','%s','%s'); return false\"><<</a></td>" % (dataset, varID, _getYearBefore(nearesttime))) str.write("<td><a href=\"#\" onclick=\"javascript:setCalendar('%s','%s','%s'); return false\"><</a></td>" % (dataset, varID, _getMonthBefore(nearesttime))) str.write("<td colspan=\"3\">%s</td>" % _getHeading(nearesttime)) str.write("<td><a href=\"#\" onclick=\"javascript:setCalendar('%s','%s','%s'); return false\">></a></td>" % (dataset, varID, _getMonthAfter(nearesttime))) str.write("<td><a href=\"#\" onclick=\"javascript:setCalendar('%s','%s','%s'); return false\">>></a></td>" % (dataset, varID, _getYearAfter(nearesttime))) str.write("</tr>") # Add the day-of-week headings str.write("<tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr>") # Add the calendar body tValIndex = 0 # index in tvalues array for week in calendar.monthcalendar(nearesttime[0], nearesttime[1]): str.write("<tr>") for day in week: if day > 0: # Search through the t axis and find out whether we have # any data for this particular day found = 0 calendarDay = (nearesttime[0], nearesttime[1], day, 0, 0, 0, 0, 0, 0) while not found and tValIndex < len(tValues): axisDay = time.gmtime(tValues[tValIndex]) res = _compareDays(axisDay, calendarDay) if res == 0: found = 1 # Found data on this day elif res < 0: tValIndex = tValIndex + 1 # Date on axis is before target day else: break # Date on axis is after target day: no point searching further if found: tValue = iso8601.tostring(tValues[tValIndex]) prettyTValue = time.strftime(prettyDateFormat, axisDay) str.write("<td id=\"t%d\"><a href=\"#\" onclick=\"javascript:getTimesteps('%s','%s','%d','%s','%s'); return false\">%d</a></td>" % (tValIndex, dataset, varID, tValIndex, tValue, prettyTValue, day)) else: str.write("<td>%d</td>" % day) else: str.write("<td></td>") str.write("</tr>") str.write("</tbody></table>") str.write("</calendar>") str.write("</root>") s = str.getvalue() str.close() return s
def getMap(req, params, config): """ The GetMap operation. req = mod_python request object (or FakeModPythonRequestObject from Jython servlet) params = wmsUtils.RequestParser object containing the request parameters config = configuration object """ _checkVersion(params) # Checks the VERSION parameter layers = params.getParamValue("layers").split(",") if len(layers) > getLayerLimit(): raise WMSException("You may only request a maximum of " + str(getLayerLimit()) + " layer(s) simultaneously from this server") styles = params.getParamValue("styles").split(",") # We must either have one style per layer or else an empty parameter: "STYLES=" if len(styles) != len(layers) and styles != ['']: raise WMSException("You must request exactly one STYLE per layer, or use" + " the default style for each layer with STYLES=") for style in styles: if style != "": # TODO: handle styles properly raise StyleNotDefined(style) # RequestParser replaces pluses with spaces: we must change back # to parse the format correctly format = params.getParamValue("format").replace(" ", "+") if format not in getSupportedImageFormats(): raise InvalidFormat("image", format, "GetMap") exception_format = params.getParamValue("exceptions", "XML") if exception_format not in getSupportedExceptionFormats(): raise InvalidFormat("exception", exception_format, "GetMap") zValue = params.getParamValue("elevation", "") if len(zValue.split(",")) > 1 or len(zValue.split("/")) > 1: raise WMSException("You may only request a single value of ELEVATION") tValue = params.getParamValue("time", "") if len(tValue.split(",")) > 1 or len(tValue.split("/")) > 1: # TODO: support animations raise WMSException("You may only request a single value of TIME") # Get the requested transparency and background colour for the layer trans = params.getParamValue("transparent", "false").lower() if trans == "false": transparent = 0 elif trans == "true": transparent = 1 else: raise WMSException("The value of TRANSPARENT must be \"TRUE\" or \"FALSE\"") bgc = params.getParamValue("bgcolor", "0xFFFFFF") if len(bgc) != 8 or not bgc.startswith("0x"): raise WMSException("Invalid format for BGCOLOR") try: bgcolor = eval(bgc) # Parses hex string into an integer except: raise WMSException("Invalid format for BGCOLOR") # Get the extremes of the colour scale scaleMin, scaleMax = _getScale(params) # Get the percentage opacity of the map layer: another WMS extension opa = params.getParamValue("opacity", "100") try: opacity = int(opa) except: raise WMSException("The OPACITY parameter must be a valid number in the range 0 to 100 inclusive") if opacity < 0 or opacity > 100: raise WMSException("The OPACITY parameter must be a valid number in the range 0 to 100 inclusive") # Find the source of the requested data location, varID, queryable = _getLocationAndVariableID(layers, config.datasets) if format == _getGoogleEarthFormat(): # This is a special case: we don't actually render the image, # we just return a KML document containing a link to the image # Set a suggested filename in the header # "inline" means "don't force a download dialog box in web browser" req.headers_out["Content-Disposition"] = "inline; filename=%s.kml" % layers[0].replace("/", "_") req.content_type = format req.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") req.write("<kml xmlns=\"http://earth.google.com/kml/2.0\">") req.write("<Folder>") req.write("<visibility>1</visibility>") req.write("<GroundOverlay>") # Get the variable metadata vars = datareader.getVariableMetadata(location) req.write("<name>%s</name>" % vars[varID].title) req.write("<description>%s</description>" % vars[varID].abstract) req.write("<visibility>1</visibility>") req.write("<Icon><href>") req.write("http://%s%s?SERVICE=WMS&REQUEST=GetMap" % (req.server.server_hostname, req.unparsed_uri.split("?")[0])) req.write("&VERSION=%s" % wmsUtils.getWMSVersion()) req.write("&LAYERS=%s" % layers[0]) if styles == ['']: req.write("&STYLES=") else: req.write("&STYLES=%s" % styles[i]) # TODO: get the FORMAT string properly req.write("&FORMAT=image/png&CRS=CRS:84") bboxEls = tuple([str(f) for f in _getBbox(params)]) req.write("&BBOX=%s,%s,%s,%s" % bboxEls) if zValue != "": req.write("&ELEVATION=%s" % zValue) if tValue != "": req.write("&TIME=%s" % tValue) # TODO get width and height more intelligently req.write("&WIDTH=500&HEIGHT=500") if not (scaleMin == 0.0 and scaleMax == 0.0): # TODO add an auto-scaled layer req.write("&SCALE=%s,%s" % (scaleMin, scaleMax)) req.write("</href></Icon>") req.write("<LatLonBox id=\"1\">") req.write("<west>%s</west><south>%s</south><east>%s</east><north>%s</north>" % bboxEls) req.write("<rotation>0</rotation>") req.write("</LatLonBox>") req.write("</GroundOverlay>") req.write("</Folder>") req.write("</kml>") else: # Generate a grid of lon,lat points, one for each image pixel grid = _getGrid(params, config) # Read the data for the image picData = datareader.readImageData(location, varID, tValue, zValue, grid, _getFillValue()) # TODO: cache the data array # Turn the data into an image and output to the client graphics.makePic(req, format, picData, grid.width, grid.height, _getFillValue(), transparent, bgcolor, opacity, scaleMin, scaleMax) return