[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