[Geomoose-users] OpenWebGIS
Brent Fraser
bfraser at geoanalytic.com
Mon Dec 21 20:05:17 PST 2015
I think it is a lot of work to do a QGIS plugin to generate a
Geomoose system from a QGIS project; just to get new implementers over
the initial hump. I would focus just on generating the
Geomoose-specific files (mapbook snippets, templates) and a bare-bones
map file, just enough to see their data.
Couple of hundred lines of Python. No big deal... ;) Extra credit for
web enabling and hosting it.
- allow only a shapefile in a zip file
- check for a .prj file (80% of the errors)
- generate a set the initial web map extent to the shapefile extent.
I've attached my crappy Python to get you started....
Best Regards,
Brent Fraser
On 12/21/2015 9:20 AM, Dan Little wrote:
> Does anyone keep tabs on QGIS' Web Mapping tool? They were trying to
> do some mapnik/WSGI thing a few years ago that I thought was somewhat
> exciting. Otherwise, working with QGIS could involve a few steps:
>
> 1. Parsing the QGIS project files (easy enough IIRC)
> 2. Collecting/normalizing the data.
> 3. making a mapbook.
> 4. generating mapfiles (probably the hard part for anything complex).
>
> BrianF and I have discussed this idea quite a few times over the years
> but have never been able to get enough funding/interest to really move
> it forward. Once people get over the basic threshold of learning
> mapfiles they don't seem to be bothered much.
>
>
>
> On Wed, Dec 16, 2015 at 12:17 PM, James Klassen <klassen.js at gmail.com> wrote:
>> I did a Rails app years back that did this too. Upload a shapefile and
>> dynamically generated a mapbook and mapfile. The styling was all SLD and a
>> default style was generated based on the shapefile type but had to be hand
>> edited from there.
>>
>> On Dec 16, 2015 12:15 PM, "Brent Fraser" <bfraser at geoanalytic.com> wrote:
>>> Eli,
>>> I totally agree with your 90% approach to support.
>>>
>>> Not too sure about the QGIS extension option. I've used an existing
>>> plugin to generate map files and was not impressed. It had some trivial
>>> bugs, but the main problem was the level of coding needed to replicate a
>>> QGIS project in a map file with all the coordinate systems and styling
>>> options (and mapserver versions!).
>>>
>>> To some extent, I would have a similar problem with my Python code, but
>>> its main purpose is just to get the mapbook and template syntax right.
>>>
>>> Best Regards,
>>> Brent Fraser
>>>
>>> On 12/16/2015 10:28 AM, Eli Adam wrote:
>>>> On Wed, Dec 16, 2015 at 8:53 AM, Brent Fraser <bfraser at geoanalytic.com>
>>>> wrote:
>>>>> Hey Dan,
>>>>>
>>>>> Some of their features are intriguing:
>>>>> View -> Timeline
>>>>> Project -> Save/Load
>>>>> About -> Game
>>>>>
>>>>> And
>>>>> Edit -> Upload Shapefile
>>>>>
>>>>> That last one is interesting as I had built some Python to take a
>>>>> shapefile and generate a GeoMoose-friendly Maperver .map file and
>>>>> snippets
>>>>> for Geomoose's mapbook.xml (when answering some support questions the
>>>>> person
>>>>> frequently sends me a shapefile and a plea for help). But shipping the
>>>>> Python to users would just generate more support questions. Maybe the
>>>>> answer is to host the Python (or the PHP equivalent) on the Geomoose.org
>>>>> site?
>>>> A QGIS extension that exports the same might be an option as well.
>>>>
>>>> For users I support, the goal is to take 90% of the users to what they
>>>> need with GeoMoose. The remaining 10% getting the higher effort to do
>>>> custom one-off projects for them, design some other process and
>>>> system, or install and train them in QGIS or ArcMap. Trying to
>>>> shoehorn the last 10% into your all purpose tool takes way more than
>>>> 10% of the effort and is not worth it. Or at least that is my
>>>> approach.
>>>>
>>>> Eli
>>>>
>>>>
>>>
>>> _______________________________________________
>>> Geomoose-users mailing list
>>> Geomoose-users at lists.osgeo.org
>>> http://lists.osgeo.org/mailman/listinfo/geomoose-users
>>
>> _______________________________________________
>> Geomoose-users mailing list
>> Geomoose-users at lists.osgeo.org
>> http://lists.osgeo.org/mailman/listinfo/geomoose-users
-------------- next part --------------
#!/usr/bin/env python
#******************************************************************************
#
# Project:
# Purpose: Create GeoMoose and Mapserver files from a shapefile.
# Author: Brent Fraser
# ToDo:
# - add select, search, and popup templates
# - check for existence of .prj file (warning msg?)
# - check for shapefile correctness: good shx, same number of records (does OGR?)
# - add label item name
# - add option for map root. (or using settings.ini?)
# - add option to skip qix building
# - add option for label item
# - prevent overwrite of existing .map file
#
# 2014-12-04 BF Initial coding
# 2015-03-09 BF Added WMS bits for Leaflet tile request compatibility
try:
from osgeo import gdal
from osgeo import ogr
except ImportError:
import gdal
import ogr
import sys
import os
def Usage():
print 'Usage: data2map.py [-classify <attribute_name>] <vector_file>'
# print 'Usage: data2map.py [-settings <settings_file>] [-classify <attribute_name>] <vector_file>'
sys.exit(1)
def writeMapFile(fMapfile, substitutions):
# Valid for Mapserver 6.0.3 (MS4W 3.0.6)
template = """MAP
# DEBUG 5
# CONFIG "MS_ERRORFILE" "/ms4w/tmp/ms_error.txt"
# CONFIG "PROJ_LIB" "\ms4w\proj\nad"
# CONFIG "CPL_DEBUG" "ON"
# CONFIG "PROJ_DEBUG" "ON"
INCLUDE "../../geomoose_globals.map"
# FONTSET "../../fonts/fontset.list"
SIZE 600 600
STATUS ON
PROJECTION # Web Merc:
"{TPL_SRS}"
END # of PROJECTION
UNITS {TPL_Units} # [dd|feet|inches|kilometers|meters|miles|nauticalmiles|percentages|pixels]
EXTENT {TPL_Extent}
WEB
METADATA
"ows_enable_request" "*"
'ows_title' 'Maps'
'ows_srs' 'EPSG:26915 EPSG:4326 EPSG:900913 EPSG:3857'
'ows_enable_request' '*'
'ows_onlineresource' 'http://localhost/cgi-bin/mapserv.exe?'
'wms_feature_info_mime_type' 'text/html'
"wms_title" "WMS Server"
"wms_onlineresource" 'http://localhost/cgi-bin/mapserv.exe?'
"wms_srs" "EPSG:3857 EPSG:4269 EPSG:900913"
END # of METADATA
END # of WEB
LEGEND
STATUS ON
LABEL
TYPE TRUETYPE
FONT vera_sans
SIZE 8
COLOR 0 0 0
END # of LABEL
END # of LEGEND
LAYER NAME "{TPL_layerName}"
# ---------- Connection: ----------
DATA "./{TPL_layerName}.shp"
# PROCESSING "CLOSE_CONNECTION=DEFER"
# FILTER "" # eg ([PARC_CODE] == 1)
# GROUP "" # [groupname]
TYPE {TPL_geomType}
STATUS DEFAULT
# -- Good tile labels:
POSTLABELCACHE TRUE # [true|false] (render layer after all labels in the cache have been drawn)
PROCESSING "LABEL_NO_CLIP=True"
# MAXSCALEDENOM 50000 # (LAYER is drawn at this number or smaller.)
# MINSCALEDENOM 6000 # (LAYER is drawn at this number or bigger.)
# REQUIRES # [expression]
# ---------- coordinates: ----------
# PROJECTION
# AUTO # the shapefile has a .prj file
# END # of PROJECTION
# UNITS meters # [dd|feet|inches|kilometers|meters|miles|nauticalmiles|percentages|pixels]
# TRANSFORM FALSE # [true|false ul|uc|ur|lc|cc|lr|ll|lc|lr]
# ---------- Rendering: ----------
OPACITY 100 # 0-100 (0=opaque), or ALPHA
# LABELCACHE OFF
# LABELREQUIRES "" # [layername]
CLASS
# NAME "All" # Uncomment to show in legend
# EXPRESSION ('[{TPL_classAttribName}]' eq '') # Attribute Choices: {TPL_AttribNames}
# Possible Classes: {TPL_classAttribValues}
STYLE
OPACITY 100 # 0-100 (0=opaque), or ALPHA
COLOR 0 0 0 # -1 -1 -1 is transparent
WIDTH 2
OUTLINECOLOR 180 180 180
OUTLINEWIDTH 1 # 9n pixels on one side of the line,point)
# SYMBOL ''circle' # [integer|string|filename|url|attribute]
# LINECAP ROUND # BUTT|ROUND|SQUARE
# LINEJOIN ROUND # ROUND|MITER|BEVEL
PATTERN 2 5 END # dashed line: 2 pixels ON, 5 pixels OFF
# ANGLE 0 # [double|attribute|AUTO]
# GAP 1 # <double> (GAP specifies the distance between SYMBOLs for LINEs and POLYGONs)
END # of STYLE
# LABELMAXSCALEDENOM 50000
TEXT '[{TPL_classAttribName}]' # Attribute Choices: {TPL_AttribNames}
LABEL
TYPE TRUETYPE
FONT vera_sans # (see fontset.list for other choices) ([attrib] also possible)
ANTIALIAS TRUE # (smooth the edges)
SIZE 8 # <double>|[attribute] (size in pixels)
POSITION CC # [UL|UC|UR|CL|CC|CR|LL|LC|LR|AUTO] (Position of label relative to labelling point)
# OFFSET 0 0 # [x][y] (Offset values in pixels for labels, from the lower left corner of label and the label point)
# MAXSIZE 256 # <double> Maximum font size to use when scaling text (in pixels)
# MINSIZE 4 # <double> Minimum font size to use when scaling text (in pixels).
COLOR 80 0 0 # (fill color)
OUTLINECOLOR 254 254 254 # rgb|[attribute] (Color to draw an outline around the characters)
OUTLINEWIDTH 4 # (outline width in pixels)
# SHADOWCOLOR 0 0 0 # Color of drop shadow.
# SHADOWSIZE 2 2 # [x][y]|[attribute][attribute]
# -- red background rectangle (billboard), with a "shadow" in grey:
# STYLE
# GEOMTRANSFORM 'labelpoly' # labelpnt|labelpoly
# COLOR 153 153 153
# OFFSET 3 2
# END # of STYLE
# STYLE
# GEOMTRANSFORM 'labelpoly'
# COLOR 255 0 0
# END # of STYLE
# -------
# WRAP " " # (split label text into multiple lines at this character)
# MAXLENGTH 10 # (WRAP after this number of characters)
# ALIGN LEFT # LEFT|RIGHT|CENTER (when WRAP is used to render labels as multiline)
ANGLE FOLLOW # <double>|AUTO|AUTO2|FOLLOW|[attribute]
MAXOVERLAPANGLE 22.5 # (doesn't draw label if it bends more than this)
BUFFER 3 # (label collision avoidance distance in pixels)
MINDISTANCE 2 # (Minimum distance between duplicate labels. Given in pixels.)
# PRIORITY 1 # <integer: 1 to 10>|[attribute] (highest priority levels rendered first.)
# FORCE TRUE # (Forces labels for this class on, regardless of collisions.)
PARTIALS FALSE # FALSE for "tile" requests
# REPEATDISTANCE 100 # (label is repeated this distance, in pixels)
# MAXSCALEDENOM 50000 # (Minimum scale at which this LABEL is drawn.)
# MINSCALEDENOM 1 # (Maximum scale at which this LABEL is drawn)
# MINFEATURESIZE 5 # <integer>|AUTO (Minimum size in pixels a feature must be to be labelled.)
END # of Label
END # of CLASS
# ---------- Attributes: ----------
TOLERANCE {TPL_mapTolerance} # use >0 for TYPE LINE, POINT
TOLERANCEUNITS PIXELS
METADATA
"identify_record" "{TPL_layerName}_identify.html"
# "popups" "{TPL_layerName}_popup.html"
# See Search Parcel service:
# 'itemquery' '{TPL_layerName}_search_result.html'
# 'itemquery-filter' '/.*[qstring].*/i'
'qstring_validation_pattern' '.'
# Feature reports are stored in the conf/feature_report directory.
# 'feature_report' '{TPL_layerName}_report.xml'
# See Select Features service:
# 'select_header' '{TPL_layerName}_select_header.html'
# 'select_record' '{TPL_layerName}_select_result.html'
END # of METADATA
# this is required for WMS popups.
TEMPLATE "{TPL_layerName}_popup.html"
END # of LAYER
END # of MAP
"""
fMapfile.write(template.format(**substitutions))
# if layerGeomType == ogr.wkbPolygon:
# ================================================================================
if __name__ == '__main__':
srcfile = None
settingsFile = None
classifyAttrib = None
argv = gdal.GeneralCmdLineProcessor( sys.argv )
if argv is None:
sys.exit( 0 )
# ------ Parse command line arguments: -------
i = 1
while i < len(argv):
arg = argv[i]
if arg == '-settings':
settingsFile = argv[i+1]
i = i + 1
elif arg == '-classify':
classifyAttrib = argv[i+1]
i = i + 1
elif arg[0] == '-':
Usage()
elif srcfile is None:
srcfile = arg
else:
Usage()
i = i + 1
if srcfile is None:
Usage()
ogr.UseExceptions()
# ------ Open Source file: ------
driver = ogr.GetDriverByName('ESRI Shapefile')
dataSource = driver.Open(srcfile, 0) # 0 means read-only. 1 means writeable.
fullPath = os.path.abspath(srcfile)
# print fullPath
shapepath,shapeext=os.path.splitext(fullPath)
if dataSource is None:
print 'Could not open %s' % (srcfile)
sys.exit(1)
# -----------------------------------------------------
# ------- Show Properties: -------
# -----------------------------------------------------
print '-------------------------------------'
print 'Source: %s' % (srcfile)
layer = dataSource.GetLayer()
layerDefinition = layer.GetLayerDefn()
print 'Name: %s' % (layer.GetName())
# or layer = dataSource.GetLayer(0)
# ...... Number of Features: ......
featureCount = layer.GetFeatureCount()
print "Number of features: %d" % (featureCount)
# ...... Geometry Type: ......
feature = layer.GetFeature(0)
geometry = feature.GetGeometryRef()
print 'Type: %s' % (geometry.GetGeometryName())
layerGeomType = geometry.GetGeometryType()
layer.ResetReading()
# ...... Extent, SRS: ......
print '-------------------------------------'
extent = layer.GetExtent()
print 'Extent:', extent
#print 'UL:', extent[0], extent[3]
#print 'LR:', extent[1], extent[2]
ms_extent = str(extent[0]) + ' ' + str(extent[2]) + ' ' + str(extent[1]) + ' ' + str(extent[3])
spatialRef = layer.GetSpatialRef()
spatialRefProj4 = spatialRef.ExportToProj4()
print spatialRefProj4
if spatialRef.IsGeographic():
units = spatialRef.GetAngularUnits()
if 'Degree' == spatialRef.GetAttrValue('UNIT',0):
unitsName = 'DD'
else:
print 'WARNING: data source units unsupported:'
print spatialRef.GetAttrValue('UNIT',0)
elif spatialRef.IsProjected():
units = spatialRef.GetLinearUnits() # [dd|feet|inches|kilometers|meters|miles|nauticalmiles]
if 'Meter' == spatialRef.GetAttrValue('UNIT',0):
unitsName = 'METERS'
else:
print 'WARNING: data source units unsupported:'
print spatialRef.GetAttrValue('UNIT',0)
else:
print 'Layer units not available'
# ...... attrib. names types ......
attribNames = []
print '-------------------------------------'
print "Name\t\tType\tWidth\tPrecision"
for i in range(layerDefinition.GetFieldCount()):
fieldName = layerDefinition.GetFieldDefn(i).GetName()
attribNames.append(fieldName)
fieldTypeCode = layerDefinition.GetFieldDefn(i).GetType()
fieldType = layerDefinition.GetFieldDefn(i).GetFieldTypeName(fieldTypeCode)
fieldWidth = layerDefinition.GetFieldDefn(i).GetWidth()
GetPrecision = layerDefinition.GetFieldDefn(i).GetPrecision()
if GetPrecision > 0:
print fieldName + "\t" + fieldType+ "\t" + str(fieldWidth) + "\t" + str(GetPrecision)
else:
print fieldName + "\t" + fieldType+ "\t" + str(fieldWidth)
# -----------------------------------------------------
# ------- build Mapserver compatible index: ------
# -----------------------------------------------------
print
print '===================================================='
print 'Building spatial index (.qix) on shapefile...'
sql = 'CREATE SPATIAL INDEX ON %s' % layer.GetName()
layerResult = dataSource.ExecuteSQL(sql)
dataSource.ReleaseResultSet(layerResult)
# -----------------------------------------------------
# ------- Show classification results: ------
# -----------------------------------------------------
classAttribValues = []
if classifyAttrib is None:
classifyAttrib = attribNames[0]
else:
print '===================================================='
print 'Classification of %s:' % (classifyAttrib)
sql = 'SELECT DISTINCT %s FROM %s' % (classifyAttrib,layer.GetName())
layerResult = dataSource.ExecuteSQL(sql)
for feature in layerResult:
if feature.GetField(0):
sql = 'SELECT COUNT(*) FROM %s WHERE %s = "%s"' %(layer.GetName(),classifyAttrib,feature.GetField(0))
else:
sql = 'SELECT COUNT(*) FROM %s WHERE %s is NULL ' %(layer.GetName(),classifyAttrib)
countResult = dataSource.ExecuteSQL(sql)
count = countResult.GetNextFeature()
if feature.GetField(0):
print "%s %s" % (feature.GetField(0),str(count.GetField(0)))
else:
print "<null> %s" % (str(count.GetField(0)))
classAttribValues.append(feature.GetField(0))
dataSource.ReleaseResultSet(countResult)
dataSource.ReleaseResultSet(layerResult)
# -----------------------------------------------------
# Translate to Mapserver map objects:
# -----------------------------------------------------
layerName = layer.GetName()
if layerGeomType in (ogr.wkbPoint, ogr.wkbMultiPoint, ogr.wkbPoint25D, ogr.wkbMultiPoint25D):
mapLayerType = 'POINT'
mapTolerance = '2'
elif layerGeomType in (ogr.wkbLineString, ogr.wkbMultiLineString, ogr.wkbLineString25D, ogr.wkbMultiLineString25D):
mapLayerType = 'LINE'
mapTolerance = '2'
elif layerGeomType in (ogr.wkbPolygon, ogr.wkbMultiPolygon, ogr.wkbPolygon25D, ogr.wkbMultiPolygon25D):
mapLayerType = 'POLYGON'
mapTolerance = '0'
substitutions = {
"TPL_layerName":layerName,
"TPL_geomType":mapLayerType,
"TPL_SRS":spatialRefProj4,
"TPL_Units":unitsName,
"TPL_Extent":ms_extent,
"TPL_mapTolerance":mapTolerance,
"TPL_AttribNames":attribNames,
"TPL_classAttribName":classifyAttrib,
"TPL_classAttribValues":classAttribValues
}
# -----------------------------------------------------
# Write Mapserver map, template files:
# -----------------------------------------------------
with open(shapepath + '.map', 'w') as fMapfile:
writeMapFile(fMapfile, substitutions)
fMapfile.closed
with open(shapepath + '_identify.html', 'w') as fHtmlfile:
fHtmlfile.write('<!-- MapServer Template -->\n')
for name in attribNames:
fHtmlfile.write('<tr><td align="left"><b>{0}:</b></td><td>[{1}]</td></tr>\n'.format(name,name))
fHtmlfile.write('<tr><td colspan="2"><hr/></td></tr>\n')
fHtmlfile.closed
# ------ Write GeoMoose mapbook.xml fragment: -------
print
print '===================================================='
print '============= GeoMoose Mapbook.xml: ================'
print ' <map-source name="MS_%s" type="mapserver" opacity="1.0">' % (layer.GetName())
print ' <file>./%s.map</file>' % (layer.GetName()) # TBD: create path from shapefile
print ' <layer name="%s" reference="false"/>' % (layer.GetName())
print ' </map-source>'
print
print ' <layer title="%s" src="MS_%s/%s" status="off" legend="false" show-legend="true" expand="true" fade="false" unfade="false" popups="false"/>' % (layer.GetName(),layer.GetName(),layer.GetName())
More information about the Geomoose-users
mailing list