This page last changed on Jul 05, 2006 by yecarrillo.

Labeling Example using InlineFeature

We're going to build a mini-application of a world map; with labeled countries, cities, and a "Dave is Here" label.

1. A red "Dave is Here" label. I'm currently on Phu Quoc Island, in VietNam. This should always be on the map. We'll be implementing this with an inline feature with SLD-POST (see below).
2. Country names labeled (high priority) with lots of space around them so they stick out.
3. City names (>1,000,000 population). Larger bold face font, with a little space around them.
4. City names (100,000 to 1,000,000 pop). smaller font, no space around.

I've processed two datasets for this:

a) Country Outlines ("countries")

  • The basis for this dataset is the polbnda layer from VMAP0
  • I've heavly processed the spatial layer so that its valid and useable
  • I've generalized it to make the dataset smaller
  • I've combined it with some the USGS GNIS information so that each country is named (ie. 'Vietnam' instead of 'VN').
  • I've combined it with Stefan Helders' (www.world-gazetteer.com) population dataset

b) World Cities ("gnis_pop")

  • from Stefan Helders' (www.world-gazetteer.com) population dataset
  • I've converted it from UTF-8 to ASCII for simplicity

The polybnda layer from VMAP0 is (c) ESRI and for non-commercial use – see the attached VMAP0_readme1.txt.

The population data is (c) by Stefan Helders www.world-gazetteer.com

The countries dataset looks like:

Column        |       Type       |  Remark
--------------+-------------------------------
 na2          | character varying| 2 letter name code (VMAP0)
 gen1         | geometry         | geometry - generalized (VMAP0)
 country_name | text             | country name (long) - from GNIS
 approx_pop   | integer          | population from world-gazetteer

The gnis_pop dataset looks like:

Column      |   Type   | Remark
------------+----------+-----------
 the_geom   | geometry | geometry (point)
 population | integer  | approximate population
 country    | text     | country name
 type       | text     | "place"
 name       | text     | ASCII version of name (ND)

Setting up the countries dataset SLD

For the countries data, we want to:

  • always have the countries displayed
  • not have country name labels on if the map is 'very zoomed out' (or it will appear too cluttered)
  • not have country name labels on if the map is 'very zoomed in' (we will rely on city names - see below)
  • have a white 'halo' around the country names so they stand out
  • we want the label centered on the country's centroid
  • have high-population countries to labeled in preference to low-population countries.
  • have a little space between country names so the map doesnt look too cluttered
<StyledLayerDescriptor version="1.0.0" 
	xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" 
	xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" 
	xmlns:xlink="http://www.w3.org/1999/xlink" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer> <Name> world_poly </Name>
    <UserStyle>        
        <FeatureTypeStyle>
            <FeatureTypeName>Feature</FeatureTypeName>
            
  <!-- first rule is the actual polygons.  We always want them on the screen-->
            <Rule>  
                <PolygonSymbolizer>
                    <Fill>
                        <CssParameter name="fill">
                            <ogc:Literal>#EBF8C4</ogc:Literal>
                        </CssParameter>
                        <CssParameter name="fill-opacity">
                            <ogc:Literal>1.0</ogc:Literal>
                        </CssParameter>
                    </Fill>
                    <Stroke>
                    <CssParameter name="fill">#A1CE18</CssParameter>
                    </Stroke>
                    
                </PolygonSymbolizer>
             </Rule>

  <!-- second rule is the country names 
       a) we went them centered on the polygon centroid
       b) we want a 'halo' around them so they are easier to read
       c) we put a little space around them so the map isnt cluttered
       d) priority is based on the population of the country
       e) we dont display labels if we are really zoomed out or too zoomed in
       -->
             
             
             <Rule>
                <MaxScaleDenominator>52000000</MaxScaleDenominator>
                <MinScaleDenominator>800000</MinScaleDenominator>
                <TextSymbolizer>
		    <Label>
			<ogc:PropertyName>COUNTRY_NA</ogc:PropertyName>
		    </Label>

		    <Font>
			<CssParameter name="font-family">Times New Roman</CssParameter>
			<CssParameter name="font-style">Normal</CssParameter>
			<CssParameter name="font-size">18</CssParameter>
			<CssParameter name="font-weight">bold</CssParameter>
		    </Font>

                    <!-- this centers the label on the polygon's centroid-->
		    <LabelPlacement>
		      <PointPlacement>	
			      <AnchorPoint>
				  <AnchorPointX>
				  0.5
				  </AnchorPointX>
				  <AnchorPointY>
				  0.5
				  </AnchorPointY>
				</AnchorPoint>
		      </PointPlacement>					    
		    </LabelPlacement>
		    
		     <!--  make the label easy to read-->
		    <Halo>				    
		      <Radius>
			 <ogc:Literal>2</ogc:Literal>
		      </Radius>
		      <Fill>
			<CssParameter name="fill">#FFFFFF</CssParameter>
			<CssParameter name="fill-opacity">0.85</CssParameter>				
		      </Fill>
		    </Halo>

		    <Fill>
			<CssParameter name="fill">#749A00</CssParameter>
		    </Fill>

                    <!-- render high population countries in preference to low-population countries -->
		    <Priority>
			<ogc:PropertyName>APPROX_POP</ogc:PropertyName>
		    </Priority>

		    <VendorOption name="group">no</VendorOption>
		    
		     <!-- add a little extra space around the labels so the map isnt cluttered -->
		    <VendorOption name="spaceAround">5</VendorOption>
		    
                </TextSymbolizer>
            </Rule>
        </FeatureTypeStyle>
    </UserStyle>
    </NamedLayer>
</StyledLayerDescriptor>
MinScaleDenominator and MaxScaleDenominator

The <MinScaleDenominator> and <MaxScaleDenominator> allow you to turn on-and-off rules based on the map's scale. The Scale Denominator in a 1:1,000,000 scale map is "1,000,000".
MinScaleDenominator - rule is turned off if the map is "zoomed in too much".
MaxScaleDenominator - rule is turned off if the map is "zoomed out too much".


The entire world - the scale denominator is very large, so we are not rendering the labels. If we were to draw country labels, the map would be too cluttered. At all scales the polygons are rendered.


Large Scale world map - the scale denominator is very large, so we are not rendering the labels. If we were to draw country labels, the map would be too cluttered.


Europe - the scale denominator is between the min and max levels, so we are rendering the labels.


SW Europe - the scale denominator is between the min and max levels, so we are rendering the labels.


Gibraltar - the scale denominator is between the min and max levels, so we are rendering the labels.


Gibraltar (zoomed in) - the scale denominator is smaller than the min level, so we are not rendering the labels.

Setting up the gnis_pop dataset SLD

For this dataset we want to have:

  • not show up if the map is very zoomed out (to prevent cluttering)
  • only have high-population (>1,000,000 people) show up when the map is zoomed out
  • have high-population and mid-population (between 1,000,000 and 180,000 people) when the map is zoomed in a lot
  • have high-population cities have a large, multi-coloured 'dot' and mid-population cities have a smaller single-coloured dot.
  • label high-population cities in preference to low-population cities
  • add a little extra space around high-population cities so they stand out a bit more
<?xml version="1.0" encoding="ISO-8859-1"?>
<StyledLayerDescriptor version="1.0.0" 
          xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd" 
          xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" 
          xmlns:xlink="http://www.w3.org/1999/xlink" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- a named layer is the basic building block of an sld document -->
<NamedLayer><Name>normal</Name>
<UserStyle>
    <!-- again they have names, titles and abstracts -->
  
  <Title>A boring default style</Title>
  <Abstract>A sample style that just prints out a black line for everything</Abstract>
    <!-- FeatureTypeStyles describe how to render different features -->
    <!-- a feature type for polygons -->
    <FeatureTypeStyle>
      <FeatureTypeName>Feature</FeatureTypeName>
      
    <Rule>
      <MaxScaleDenominator>17000000</MaxScaleDenominator>
      
      <!-- only for cities with > 1,000,000 population-->
      <Filter>
         <PropertyIsGreaterThan>
           <PropertyName>POPULATION</PropertyName>
           <Literal>1000000</Literal>
         </PropertyIsGreaterThan>
      </Filter>
     
          <!--double large dot - red and magenta  -->
        <PointSymbolizer>
	     <Graphic>
		<Mark>
		  <WellKnownName>circle</WellKnownName>
		  <Fill>
		    <CssParameter name="fill">#ff0000</CssParameter>
		  </Fill>
		 </Mark>
		 <Size>15.0</Size>
	     </Graphic>
	</PointSymbolizer>
	<PointSymbolizer>
	     <Graphic>
		 <Mark>
		   <WellKnownName>circle</WellKnownName>
		   <Fill>
		     <CssParameter name="fill">#ff00ff</CssParameter>
		    </Fill>
		 </Mark>
		 <Size>12.0</Size>
	       </Graphic>
	</PointSymbolizer>
        <TextSymbolizer>
	    <Label>
		<ogc:PropertyName>NAME</ogc:PropertyName>
	    </Label>

	    <Font>
		<CssParameter name="font-family">Times New Roman</CssParameter>
		<CssParameter name="font-style">Normal</CssParameter>
		<CssParameter name="font-size">18</CssParameter>
		<CssParameter name="font-weight">bold</CssParameter>
	    </Font>

       <!-- move label a little to the left so that its 'beside' the dot-->
	    <LabelPlacement>
	    <PointPlacement>
	     <Displacement>
	       <DisplacementX>
		  8
	       </DisplacementX>
		<DisplacementY>
		      0
		</DisplacementY>

	     </Displacement>
	    </PointPlacement>

		 </LabelPlacement>

    <VendorOption name="group">no</VendorOption>
    
    <!-- add a little space around the label so that the map isnt too cluttered-->
    <VendorOption name="spaceAround">3</VendorOption>

    <Fill>
		<CssParameter name="fill">#FB3C09</CssParameter>
    </Fill>
    
    <!-- give priority to high-population cities-->
    <Priority>
       <PropertyName>POPULATION</PropertyName>
    </Priority>
		    
  </TextSymbolizer>
</Rule>
      
      <Rule>
            <MaxScaleDenominator>5000000</MaxScaleDenominator>
            
            <!-- for cities between 180,000 and 1,000,000 -->
            
            <Filter>
             <And>
               <PropertyIsLessThan>
                 <PropertyName>POPULATION</PropertyName>
                 <Literal>1000000</Literal>
               </PropertyIsLessThan>
               <PropertyIsGreaterThan>
	                        <PropertyName>POPULATION</PropertyName>
	                        <Literal>180000</Literal>
               </PropertyIsGreaterThan>
               </And>
            </Filter>
            
            <!-- single red dot -->
            <PointSymbolizer>
             <Graphic>
             	<Mark>
             	 <WellKnownName>circle</WellKnownName>
             	 <Fill>
             	  <CssParameter name="fill">#ff0000</CssParameter>
             	 </Fill>
             	</Mark>
             	<Size>8.0</Size>
              </Graphic>
             </PointSymbolizer>

              <TextSymbolizer>
              <Label>
      		<ogc:PropertyName>NAME</ogc:PropertyName>
	      </Label>

	      <Font>
		<CssParameter name="font-family">Times New Roman</CssParameter>
		<CssParameter name="font-style">Normal</CssParameter>
		<CssParameter name="font-size">10</CssParameter>
	     </Font>
	     
	   <!-- move label a little to the left so that its 'beside' the dot-->
	    <LabelPlacement>
	      <PointPlacement>
		     <Displacement>
		       <DisplacementX>
			  5
		       </DisplacementX>
		       <DisplacementY>
		       0
		       </DisplacementY>

		     </Displacement>
	     </PointPlacement>
      	    </LabelPlacement>
      	   
	    <Fill>
			<CssParameter name="fill">#000000</CssParameter>
	    </Fill>

	    <VendorOption name="group">no</VendorOption>
	    
	    <!-- add a tiny bit of extra space around the label so the map isnt too cluttered-->
	    
	    <VendorOption name="spaceAround">1</VendorOption>
	    
	    <!-- give large population cities priority over low-population cities -->
	    <Priority>
	       <PropertyName>POPULATION</PropertyName>
	    </Priority>  
	    
      </TextSymbolizer>
    </Rule>
      
      
 </FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>


At this zoom level, only cities with >1,000,000 people are being shown. If there were a labeling conflict, the city with the higher population would 'win'.
If you zoom out more than this, no features will be rendered.


At this zoom level, only cities with >1,000,000 people are being shown. If you zoom out more than this, no features will be rendered.


At this zoom level, both high-population and mid-population cities are being shown.
If there were a labeling conflict, the city with the higher population would 'win'.

Combining the two layers

When these two layers are combined, the resulting map is relatively easy to read at any scale.


Countries show, country name labels shown.


Countries show, country name labels shown, high population cities shown.


Countries show, country name labels shown, high population cities shown, mid-level population cities show.


Countries show, country name labels NOT shown, high population cities shown (none present), mid-level population cities show.

Adding "Dave is Here" label

more info

See documentation on SLD-POST (or read the SLD Specification) and also the documentation on SLD-InlineFeatures.

We implement a "Dave is Here" label by extending the above. We will use an inline feature that describes my location:

<InlineFeature>
<FeatureCollection>
   <featureMember>
	<POI>
	  <Label>Dave is Here!</Label>
	  <pointProperty>
	    <gml:Point srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
			<gml:coord><gml:X>103.91</gml:X><gml:Y>10.29</gml:Y></gml:coord>
	    </gml:Point>
	  </pointProperty>
	</POI>
    </featureMember>
</FeatureCollection>	
</InlineFeature>

And this simple style (very much like the above).

<UserStyle>
<FeatureTypeStyle>
<Rule>
   <PointSymbolizer>
	 <Graphic>
	    <Mark>
		 <WellKnownName>star</WellKnownName>
		 <Fill>
		   <CssParameter name="fill">#FD6435</CssParameter>
		 </Fill>
            </Mark>
            <Size>12.0</Size>
         </Graphic>
    </PointSymbolizer>
   <TextSymbolizer>
	    <Label>
		<ogc:PropertyName>Label</ogc:PropertyName>
	    </Label>

	    <Font>
		<CssParameter name="font-family">Times New Roman</CssParameter>
		<CssParameter name="font-style">Normal</CssParameter>
		<CssParameter name="font-size">18</CssParameter>
		<CssParameter name="font-weight">bold</CssParameter>
	    </Font>

	    <LabelPlacement>
	     <PointPlacement>
	      <Displacement>
	        <DisplacementX>
		   8
	        </DisplacementX>
		<DisplacementY>
		      0
		</DisplacementY>
	      </Displacement>
	     </PointPlacement>
	    </LabelPlacement>

	     <Halo>				    
		    <Radius>
			 <ogc:Literal>2</ogc:Literal>
		    </Radius>
		    <Fill>
			<CssParameter name="fill">#FFFFFF</CssParameter>
			<CssParameter name="fill-opacity">0.85</CssParameter>				
		    </Fill>
	    </Halo>

	    <Fill>
	       <CssParameter name="fill">#FD6435</CssParameter>
	    </Fill>

	    <Priority>
	       <Literal>100000000</Literal>
	    </Priority>
	    <VendorOption name="group">no</VendorOption>
	    <VendorOption name="spaceAround">3</VendorOption>
 </TextSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>

NOTE: we use a very high priority so that this label will take presidence over all other labels.

The <GetMap> request looks like, in outline:

<ogc:GetMap ...>
 <StyledLayerDescriptor ...>
 
<!-- draw the above two layers -->
	<NamedLayer>
            <Name>topp:countries_gen1</Name>
	    <NamedStyle>
		  <Name>world_poly</Name>
	    </NamedStyle>
	</NamedLayer>
       <NamedLayer>
	    <Name>topp:gnis_pop</Name>
	    <NamedStyle>
		 <Name>world_pop</Name>
	    </NamedStyle>
       </NamedLayer>
<!-- now the dave is here label-->
       <UserLayer> 
          <Name>Inline</Name>		
	 
           <InlineFeature> 
               ... as above ...
           </InlineFeature>
	 	
	   <UserStyle>
              ... as above ...
           </UserStyle>
       </UserLayer>	
  </StyledLayerDescriptor>

<!-- normal get map parameters -->
  <BoundingBox>
   <gml:coord><gml:X>102.78575527315321</gml:X><gml:Y>8.358632381881332</gml:Y></gml:coord>
    <gml:coord><gml:X>109.06006786373088</gml:X><gml:Y>11.51749910135899</gml:Y></gml:coord>
  </BoundingBox>

  <Output>
    <Format>image/png</Format>
    <Transparent>false</Transparent>
    <Size>
        <Width>578</Width>
        <Height>291</Height>
    </Size>
   </Output>

   <Exceptions>application/vnd.ogc.se+xml</Exceptions>

</ogc:GetMap>

Find attached 3 example .XML <GetMap> requests, used to generate the above 3 images.

Running this from your server

  • geoserver_data_dir
  • applications
    a) countries only mapbuilder
    b) gnis_point only mapbuilder
    c) combined countries and gnis_point mapbuilder
    d) SLD-POST (see attached 3 .xml files)
    e) SLD_BODY= (<StyledLayerDescriptor>)
  • instructions

VMAP0_readme1.txt (text/plain)
country_5.gif (image/gif)
country_4.gif (image/gif)
country_3.gif (image/gif)
country_2.gif (image/gif)
country_1.gif (image/gif)
country_6.gif (image/gif)
gnis_3.gif (image/gif)
gnis_2.gif (image/gif)
gnis_1.gif (image/gif)
gnis_1.gif (image/gif)
combined_4.gif (image/gif)
combined_3.gif (image/gif)
combined_2.gif (image/gif)
combined_1.gif (image/gif)
dave_3.gif (image/gif)
dave_2.gif (image/gif)
dave_1.gif (image/gif)
world_pop.sld (application/octet-stream)
world_poly.sld (application/octet-stream)
dave3.xml (text/xml)
dave2.xml (text/xml)
dave1.xml (text/xml)

Hi :

This is a nicely written tutorial. I have a requirement wherein I have stored the spatial data about a campus that has got a lot of buildings and rooms. Each room has offices for people working there. We also store phone numbers in each office as part of the spatial data. The application should take the phone number from the user in an input text box and then the application should point to the office containing that phone number.

Can you drop some hints or some pointers as to how do we query the spatial data to match the input phone number. Appreciate your response.

Irshad Buchh
Senior Principal Consultant
Posted by ibuchh at Aug 26, 2007 12:10
Document generated by Confluence on Jan 16, 2008 23:27