Creating a My Ward page
Along with the standard My House and My Nearest pages which display information related to a users selected address iShare Maps also supports Area Search
pages which display information associated with an area.
The user still searches for an address like normal but when viewing an Area Search page the area which the current address falls within is used to query data. Commonly Area Search pages are used to create My Ward or My Parish pages which can be useful for giving a broader view of a given area.
This tutorial will step through creating a My Ward page using the sample workshop data commonly used for iShare Maps training.
Create a ward lookup
In order to create a My Ward page we first need to create a ward_lookup
Shapefile with the ward areas as polygons, the ward name and code. In this instance we are going to use ward polygons derived from OS BoundaryLine which is present in the database as boundaryline.wards_surrey
.
Workflow
- Create a new Workflow Job for the Tasks required to create the data for My Ward called
My Ward
- Create a new Spatial Data Transformation Task
Create ward_lookup
which convertsboundaryline.wards_surrey
in theDataShare
database to as Shapefile calledwards_lookup.shp
- Run the Task to generate the Shapefile
Mapfile
- Add a
ward_lookup
layer to your overlay mapfile for example:
LAYER NAME "ward_lookup" STATUS OFF TYPE POLYGON DATA "Overlays/ward_lookup.shp" TOLERANCE 0 METADATA qstring_validation_pattern "." qstr_validation_pattern "." END LABELITEM "name" CLASS NAME "" STYLE OUTLINECOLOR 81 167 174 WIDTH 3 END LABEL FONT "verdana" TYPE truetype SIZE 8 COLOR 255 0 0 OUTLINECOLOR 255 255 255 OUTLINEWIDTH 3 END END END
Of particular interest is:
TOLERANCE 0
- All layers used by My Ward will have a
TOLERANCE
of0
to ensure only those features that strictly intersect when searching are returned
- All layers used by My Ward will have a
- The layer is styled and includes a label which we will use later when displaying a ward map
Create the My Ward page
- Create a new
My Ward
MapSource- Call the file
MyWard.xml
- Delete all but one of the existing groups
- Call the file
- Select the main node to display the high level MapSource properties
- Ensure the MapFile Path is set to the path to your overlays mapfile
- Select the
My Ward
item in the lefthand list of MapSources then:- Select the
Nearest page
Page Type - Select the
Area Search
Search Type - Select
ward_lookup
as the Layer Name - Select
code
as the Search Field
- Select the
- Create a file called
MyWard_ward_lookup_fields.xml
in the_MapServerConfig\_MapServerTemplates
folder with the following contents:
<!-- MapServer Template --> <row LayerName="ward_lookup"> <name><![CDATA[[name]]]></name> <code><![CDATA[[code]]]></code> <MapSpurE>[shpmidx]</MapSpurE> <MapSpurN>[shpmidy]</MapSpurN> <MapSpurMinE>[shpminx]</MapSpurMinE> <MapSpurMinN>[shpminy]</MapSpurMinN> <MapSpurMaxE>[shpmaxx]</MapSpurMaxE> <MapSpurMaxN>[shpmaxy]</MapSpurMaxN> </row>
Display the ward name
So we've something to display we will create a Ward name layer.
Mapfile
- Create a new
ward_summary
layer inoverlays.map
which queries theboundaryline.wards_surrey
layer in theiShareData
database.
LAYER NAME "ward_summary" STATUS OFF TYPE POINT INCLUDE "_datashare.map" DATA "wkb_geometry from (select ogc_fid, code, name, ST_PointOnSurface(wkb_geometry) as wkb_geometry from boundaryline.wards_surrey) as foo using unique ogc_fid using srid=27700" TOLERANCE 0 METADATA qstring_validation_pattern "." qstr_validation_pattern "." END CLASS NAME "" STYLE SYMBOL "circle" COLOR 0 128 0 SIZE 8 END END END
Of note is the SQL used in the DATA
statement which creates a row per ward with a point within each ward polygon which will be used for the lookup. The PostGIS function ST_PointOnSurface
returns a POINT
guaranteed to lie on the surface of a polygon.
SELECT ogc_fid, code, name, ST_PointOnSurface(wkb_geometry) AS wkb_geometry FROM boundaryline.wards_surrey
Studio
- Create a
Summary
Layer Group- Ensure Visible on Startup is checked
- Create a
Ward name
Layer- Select the
ward_summary
Layer Name - Ensure the Show My Search and Show My option is checked
- Choose the
name
field
- Select the
It may be necessary to restart the iShare Web and WebService application pools in order to see the new My Ward page.
The My Ward page now simply displays the name of the ward that the current address falls within, not very excited but a start.
Adding a list of planning applications
Next we will display a list of planning applications which fall within the current ward. As our planning applications table contains POINT
geometries we can simply use the data as is without the need for any Workflow.
Mapfile
- Add a
ward_planning
layer tooverlays.map
which queriesplanning.surrey_planning
from theiShareData
database. Again notice that the layer has aTOLERANCE
of0
.
LAYER NAME "ward_planning" STATUS OFF TYPE POINT INCLUDE "_datashare.map" DATA "wkb_geometry from (select * from planning.surrey_planning) as foo using unique ogc_fid using srid=27700" TOLERANCE 0 METADATA qstring_validation_pattern "." qstr_validation_pattern "." END CLASS NAME "" STYLE SYMBOL "circle" COLOR 0 128 0 SIZE 8 END END END
Studio
- Create aÂ
Planning
 Layer Group- Ensure Visible on Startup is checked
- Create aÂ
Planning applications
 Layer- Select theÂ
ward_planning
 Layer Name - Ensure the Show MySearch and Show My option is checked
- Choose theÂ
title
 andÂdescription
 fields
- Select theÂ
Planning application summary
As well as simply displaying the list of planning applications we can also display a summary giving a count of planning applications.
Studio - Workflow
- Create a new Stored Procedure Task called
Create ward_planning_summary
, choose:- Database:
DataShare
- Function:
at_sys_create_table
from the Astun category (from v6.0.0 this is called wkf_create_table under the -Workflow- Function filter) - Set the tablename parameter to:
ward_planning_summary
and the selectstatement to:
- Database:
WITH counts AS ( SELECT wards.code, count(*) AS count FROM planning.surrey_planning planning, boundaryline.wards_surrey wards WHERE st_intersects(wards.wkb_geometry, planning.wkb_geometry) GROUP BY wards.code ) SELECT wards.ogc_fid, wards.code, st_pointonsurface(wards.wkb_geometry) AS wkb_geometry, 'There ' || CASE WHEN counts.count = 1 THEN 'is ' ELSE 'are ' END || coalesce(counts.count::text, 'no') || ' planning application' || CASE WHEN counts.count = 1 THEN '' ELSE 's' END AS summary FROM boundaryline.wards_surrey wards LEFT JOIN counts ON (wards.code = counts.code)
The SQL first creates a counts result set which consists of a row per ward with it's code and a count of planning applications which fall within it. This counts lookup is then joined to the wards table on ward code, the results contain ward code, a POINT within the ward, and a html column which contains a formatted summary for example: "There are 3 planning applications".
Mapfile
Create aÂ
ward_planning_summary
 layer inÂoverlays.map
LAYER NAME "ward_planning_summary" STATUS OFF TYPE POINT INCLUDE "_datashare.map" DATA "wkb_geometry from (select * from ward_planning_summary) as foo using unique ogc_fid using srid=27700" TOLERANCE 0 METADATA qstring_validation_pattern "." qstr_validation_pattern "." END CLASS NAME "" STYLE SYMBOL "circle" COLOR 0 128 0 SIZE 8 END END END
Studio - MapSource
- Create aÂ
Planning summary
 layer below theÂSummary
 group- Select theÂ
ward_planning_summary
 Layer Name - Ensure the Show MySearch and Show My option is checked
- Choose theÂ
summary
 field
- Select theÂ
Create a ward map
Currently all data is shown in My Nearest panels but lets add a map showing the current ward then update the page style via CSS to display the map and summary panels side by side at the top of the page followed by all other panels as My Nearest panels below.
Studio - Workflow
First we're going to create a table which has a row per ward with a POINT
 within the ward and a html
 field containing an img
 element with the appropriate src
 URL to generate a map zoomed to the extent of the ward.
- Create a new Stored Procedure Task calledÂ
Create ward_map
, choose:- Database:Â
DataShare
- Function:Â
at_sys_create_table
 from the Astun category (from v6.0.0 this is called wkf_create_table under the -Workflow- Function filter) - Set the tablename parameter to:Â
ward_map
 and the selectstatement to:
- Database:Â
WITH envelope AS ( SELECT ogc_fid, wkb_geometry, code, name, st_expand(st_envelope(wkb_geometry), 300) AS envelope FROM boundaryline.wards_surrey wards ), coords AS ( SELECT *, st_x(st_centroid(envelope.envelope))::int AS x, st_y(st_centroid(envelope.envelope))::int AS y, greatest((st_xmax(envelope) - st_xmin(envelope)), (st_ymax(envelope) - st_ymin(envelope)))::int AS z FROM envelope ) SELECT ogc_fid, code, name, st_pointonsurface(wkb_geometry) AS wkb_geometry, '<img class="map" src="MapGetImage.aspx?RequestType=Map&MapWidth=450&MapHeight=450&MapSource=Astun/Default&Easting=' || x || '&Northing=' || y || '&Zoom=' || z || '&mapid=-1&ServiceAction=ZoomToLocation&Layers=ward_lookup,ward_summary" alt="' || name || '" />' AS html FROM coords
The SQL first creates an envelope
 result set which includes a geometry for the bounds of each ward, the coords
 result set then extracts the centre (x
/y
) and width (z
) and finally a result set uses these values to construct a POINT
 within the ward and a html
 field containing an img
 element with the appropriate src
 URL to generate a map zoomed to the extent of the ward. The URL specifies that the ward_lookup
layer is displayed on the map so that the boundaries are visible together with labels.
Mapfile
LAYER NAME "ward_map" STATUS OFF TYPE POINT INCLUDE "_datashare.map" DATA "wkb_geometry from (select *, html as html_raw from ward_map) as foo using unique ogc_fid using srid=27700" TOLERANCE 0 METADATA qstring_validation_pattern "." qstr_validation_pattern "." END CLASS NAME "" STYLE SYMBOL "circle" COLOR 0 128 0 SIZE 8 END END END
Of note is the SQL used in the DATA
 statement which creates html_raw
 column by aliasing the existing html
 column. This allows us to use the html_raw
 column for the MapSource layer causing the raw HTML text to be output which will cause the img
 to be displayed.
Studio - MapSource
- Create aÂ
Ward map
 Layer Group- Ensure Visible on Startup is checked
- Move it up so it's the first Layer Group with theÂ
Summary
 Layer Group second
- Create aÂ
Ward map
 Layer- Select theÂ
ward_map
 Layer Name - Ensure the Show MySearch and Show My option is checked
- Choose theÂ
html_raw
 field and ensure Display field name is unchecked
- Select theÂ
Styling
To display the map and summary panels side by side at the top of the page we need a little JavaScript and CSS.
JavaScript
In order to allow us identify the first and second panels on the My Ward page we will use a little JavaScript to:
- Add a class to the page body element to allow us to identify which page is currently being displayed
- Add a class to each panel with it's position in the page
- Disable ability to collapse the first two panels
window.astun.customJS = function() { // Determine which page we are on from the selected tab var pageName = jQuery('.atTabSelected').val().replace(/\s/, '_').toLowerCase(); // Add a class to the body to allow us to create specific rules for individual pages jQuery('body').addClass(pageName); // Add a class to each panel with it's position in the page, // atPanel0, atPanel1 etc. jQuery('.atPanelContainer .atPanel').each(function(idx) { jQuery(this).addClass('atPanel' + idx); }); if (pageName === 'my_ward') { // Make the first two panels on the My Ward page non-collapsable jQuery('.atPanel.atPanel0, .atPanel.atPanel1').find("*").unbind(); // Remove the "Click to toggle" tooltip jQuery('.atPanel.atPanel0, .atPanel.atPanel1').find(".atPanelHeader").attr("title", ""); } };
CSS
The following CSS when added to one of the site specific CSS files found under the Web\custom
 directory applies the following styles:
- Display the first two panel side by side and always expanded
- Hide the headings for the summary panel
- Hide the "Details" and "Distance" table headings
- Make the ward map responsive
/* Position the first two My Ward panels */ .my_ward .atPanel.atPanel0, .my_ward .atPanel.atPanel1 { width: 48%; float:left; margin-left: 10px; } /* Always display the content of the first two panels */ .my_ward .atPanel.atPanel0 .atPanelParentContent, .my_ward .atPanel.atPanel1 .atPanelParentContent { display: block !important; } /* Ensure the third panel displays below the first two */ .my_ward .atPanel.atPanel0, .my_ward .atPanel.atPanel2 { clear: both; } /* Style headers for the first two panels */ .my_ward .atPanel.atPanel0 .atPanelHeader, .my_ward .atPanel.atPanel1 .atPanelHeader { cursor: default !important; } .my_ward .atPanel.atPanel0 .atPanelStatusSign, .my_ward .atPanel.atPanel1 .atPanelStatusSign { visibility: hidden; } /* Style panel content */ /* Hide layer titles for first two panels */ .my_ward .atPanel.atPanel0 h4, .my_ward .atPanel.atPanel1 h4 { display: none; } .my_ward table thead { display: none; } .my_ward table tr th:nth-child(1), .my_ward table tr td:nth-child(1) { width: 100%; } /* Make the ward map responsive */ .my_ward img.map { width: 100%; }
Responsive design
As a bonus lets make some small changes to make the My Ward responsive and display appropriately on a range of device sizes. In this case we can simply add a single media query to our CSS to apply a different style to the first two panels when the screen width is smaller than a given size.
@media (max-width: 640px) { .my_ward .atPanel.atPanel0, .my_ward .atPanel.atPanel1 { width: 100%; margin-left: 0; } }