SLD format rewrite. Adds a versioned parser with read and write support. This does not come with full support for ogc:expression parsing, but makes for easy future enhancements. r=ahocevar (closes #1458)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@6645 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
@@ -6,89 +6,114 @@
|
|||||||
width: 800px;
|
width: 800px;
|
||||||
height: 475px;
|
height: 475px;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
background: #ccddff;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="../lib/Firebug/firebug.js"></script>
|
<script src="../lib/Firebug/firebug.js"></script>
|
||||||
<script src="../lib/OpenLayers.js"></script>
|
<script src="../lib/OpenLayers.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var lon = 5;
|
|
||||||
var lat = 40;
|
|
||||||
var zoom = 5;
|
|
||||||
var map, layer, gmlLayers, styles, waterStyle, hover;
|
|
||||||
|
|
||||||
function load(){
|
var map, sld, gmlLayers;
|
||||||
OpenLayers.loadURL("tasmania/sld-tasmania.xml", "", null, init);
|
var format = new OpenLayers.Format.SLD();
|
||||||
|
function init() {
|
||||||
|
|
||||||
|
map = new OpenLayers.Map('map');
|
||||||
|
map.addControl(new OpenLayers.Control.LayerSwitcher());
|
||||||
|
|
||||||
|
OpenLayers.loadURL("tasmania/sld-tasmania.xml", null, null, complete);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(req){
|
function getDefaultStyle(sld, layerName) {
|
||||||
map = new OpenLayers.Map('map');
|
var styles = sld.namedLayers[layerName].userStyles;
|
||||||
layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
|
var style;
|
||||||
"http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
|
for(var i=0; i<styles.length; ++i) {
|
||||||
map.addLayer(layer);
|
style = styles[i];
|
||||||
map.zoomToExtent(new OpenLayers.Bounds(143,-39,150,-45));
|
if(style.isDefault) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
sld = new OpenLayers.Format.SLD().read(req.responseText,
|
function complete(req) {
|
||||||
{withNamedLayer: true});
|
|
||||||
|
|
||||||
styles = sld[1];
|
sld = format.read(req.responseXML || req.responseText);
|
||||||
|
var hoverStyle = sld.namedLayers["WaterBodies"].userStyles[1];
|
||||||
// for the hover style, we do not want to use the SLD default as
|
hoverStyle.defaultStyle = OpenLayers.Util.extend(
|
||||||
// base style
|
{}, OpenLayers.Feature.Vector.style["select"]
|
||||||
styles["WaterBodies"]["Hover Styler"].defaultStyle = OpenLayers.Util.extend({},
|
);
|
||||||
OpenLayers.Feature.Vector.style["select"]);
|
|
||||||
|
|
||||||
gmlLayers = [
|
gmlLayers = [
|
||||||
// use the sld UserStyle named "Default Styler"
|
// use the sld UserStyle named "Default Styler"
|
||||||
new OpenLayers.Layer.GML("StateBoundaries",
|
new OpenLayers.Layer.GML(
|
||||||
"tasmania/TasmaniaStateBoundaries.xml", {
|
"StateBoundaries",
|
||||||
styleMap: new OpenLayers.StyleMap(styles["WaterBodies"])}),
|
"tasmania/TasmaniaStateBoundaries.xml",
|
||||||
new OpenLayers.Layer.GML("Roads",
|
{
|
||||||
"tasmania/TasmaniaRoads.xml", {
|
styleMap: new OpenLayers.StyleMap(
|
||||||
styleMap: new OpenLayers.StyleMap(styles["Roads"])}),
|
getDefaultStyle(sld, "Land")
|
||||||
new OpenLayers.Layer.GML("WaterBodies",
|
),
|
||||||
"tasmania/TasmaniaWaterBodies.xml", {
|
isBaseLayer: true
|
||||||
styleMap: new OpenLayers.StyleMap(styles["WaterBodies"])}),
|
|
||||||
new OpenLayers.Layer.GML("Cities",
|
|
||||||
"tasmania/TasmaniaCities.xml", {
|
|
||||||
styleMap: new OpenLayers.StyleMap(styles["Cities"])})];
|
|
||||||
|
|
||||||
for (var i=0; i<gmlLayers.length; i++) {
|
|
||||||
map.addLayer(gmlLayers[i]);
|
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
new OpenLayers.Layer.GML(
|
||||||
|
"Roads",
|
||||||
|
"tasmania/TasmaniaRoads.xml",
|
||||||
|
{styleMap: new OpenLayers.StyleMap(getDefaultStyle(sld, "Roads"))}
|
||||||
|
),
|
||||||
|
new OpenLayers.Layer.GML(
|
||||||
|
"WaterBodies",
|
||||||
|
"tasmania/TasmaniaWaterBodies.xml",
|
||||||
|
{
|
||||||
|
styleMap: new OpenLayers.StyleMap({
|
||||||
|
"default": getDefaultStyle(sld, "WaterBodies"),
|
||||||
|
"select": hoverStyle
|
||||||
|
})
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new OpenLayers.Layer.GML(
|
||||||
|
"Cities",
|
||||||
|
"tasmania/TasmaniaCities.xml",
|
||||||
|
{styleMap: new OpenLayers.StyleMap(getDefaultStyle(sld, "Cities"))}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
hover = new OpenLayers.Control.SelectFeature(gmlLayers[2], {
|
map.addLayers(gmlLayers);
|
||||||
hover: true,
|
map.zoomToExtent(new OpenLayers.Bounds(143,-39,150,-45));
|
||||||
renderIntent: "Hover Styler"
|
|
||||||
});
|
var hover = new OpenLayers.Control.SelectFeature(
|
||||||
|
gmlLayers[2], {hover: true}
|
||||||
|
);
|
||||||
map.addControl(hover);
|
map.addControl(hover);
|
||||||
hover.activate();
|
hover.activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// set a new style when the radio button changes
|
// set a new style when the radio button changes
|
||||||
function setStyle(styleName) {
|
function setStyle(index) {
|
||||||
gmlLayers[2].styleMap.styles["default"] = styles["WaterBodies"][styleName];
|
gmlLayers[2].styleMap.styles["default"] = sld.namedLayers["WaterBodies"].userStyles[index];
|
||||||
// change the style of the features of the WaterBodies layer
|
// change the style of the features of the WaterBodies layer
|
||||||
gmlLayers[2].redraw();
|
gmlLayers[2].redraw();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body onload="load()">
|
<body onload="init()">
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<p>This example uses a <a target="_blank" href="tasmania/sld-tasmania.xml">SLD
|
<p>This example uses a <a target="_blank" href="tasmania/sld-tasmania.xml">SLD
|
||||||
file</a> to style the vector features. The style to be used is either
|
file</a> to style the vector features. To construct layers that use styles
|
||||||
determined by the NamedLayer and IsDefault properties in the sld file, or
|
from SLD, create a StyleMap for the layer that uses one of the userStyles in the
|
||||||
can directly be applied by addressing a style from the styles
|
namedLayers object of the return from format.read().</p>
|
||||||
hash with the UserStyle name from the sld file as key. Select a new style for the WaterBodies layer below:<p>
|
<p>Select a new style for the WaterBodies layer below:</p>
|
||||||
<form>
|
<form>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" checked="checked" value="default">Default Styler (zoom in to see more features)</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" checked="checked" value="0">Default Styler (zoom in to see more features)</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test PropertyIsEqualTo">Styler Test PropertyIsEqualTo</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="3">Styler Test PropertyIsEqualTo</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test Not FeatureId">Styler Test Not FeatureId</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="4">Styler Test WATER_TYPE</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test WATER_TYPE">Styler Test WATER_TYPE</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="5">Styler Test PropertyIsGreaterThanOrEqualTo</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test PropertyIsGreaterThanOrEqualTo">Styler Test PropertyIsGreaterThanOrEqualTo</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="6">Styler Test PropertyIsLessThanOrEqualTo</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test PropertyIsLessThanOrEqualTo">Styler Test PropertyIsLessThanOrEqualTo</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="7">Styler Test PropertyIsGreaterThan</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test PropertyIsGreaterThan">Styler Test PropertyIsGreaterThan</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="8">Styler Test PropertyIsLessThan</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test PropertyIsLessThan">Styler Test PropertyIsLessThan</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="9">Styler Test PropertyIsLike</input><br/>
|
||||||
<input type="radio" name="style" onclick="setStyle(this.value)" value="Styler Test PropertyIsLike">Styler Test PropertyIsLike</input><br/>
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="10">Styler Test PropertyIsBetween</input><br/>
|
||||||
|
<input type="radio" name="style" onclick="setStyle(this.value)" value="11">Styler Test FeatureId</input><br/>
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<sld:StyledLayerDescriptor xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<sld:StyledLayerDescriptor version="1.0.0"
|
||||||
|
xmlns:sld="http://www.opengis.net/sld"
|
||||||
|
xmlns:ogc="http://www.opengis.net/ogc"
|
||||||
|
xmlns:gml="http://www.opengis.net/gml"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">
|
||||||
<sld:NamedLayer>
|
<sld:NamedLayer>
|
||||||
<sld:Name>WaterBodies</sld:Name>
|
<sld:Name>WaterBodies</sld:Name>
|
||||||
<sld:UserStyle>
|
<sld:UserStyle>
|
||||||
@@ -55,7 +61,26 @@
|
|||||||
<sld:Name>testRuleNameElse</sld:Name>
|
<sld:Name>testRuleNameElse</sld:Name>
|
||||||
<sld:Title>title</sld:Title>
|
<sld:Title>title</sld:Title>
|
||||||
<sld:Abstract>Abstract</sld:Abstract>
|
<sld:Abstract>Abstract</sld:Abstract>
|
||||||
<ogc:ElseFilter/>
|
<sld:ElseFilter/>
|
||||||
|
<sld:PolygonSymbolizer>
|
||||||
|
<sld:Fill>
|
||||||
|
<sld:CssParameter name="fill">#aaaaff</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="fill-opacity">
|
||||||
|
<ogc:Literal>0.5</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
</sld:Fill>
|
||||||
|
<sld:Stroke>
|
||||||
|
<sld:CssParameter name="stroke">
|
||||||
|
<ogc:Literal>#C0C0C0</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-opacity">
|
||||||
|
<ogc:Literal>1</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-width">
|
||||||
|
<ogc:Literal>1</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
</sld:Stroke>
|
||||||
|
</sld:PolygonSymbolizer>
|
||||||
</sld:Rule>
|
</sld:Rule>
|
||||||
</sld:FeatureTypeStyle>
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
@@ -123,44 +148,35 @@
|
|||||||
<sld:Name>testRuleNameHoverElse</sld:Name>
|
<sld:Name>testRuleNameHoverElse</sld:Name>
|
||||||
<sld:Title>title</sld:Title>
|
<sld:Title>title</sld:Title>
|
||||||
<sld:Abstract>Abstract</sld:Abstract>
|
<sld:Abstract>Abstract</sld:Abstract>
|
||||||
<ogc:ElseFilter/>
|
<sld:ElseFilter/>
|
||||||
</sld:Rule>
|
|
||||||
</sld:FeatureTypeStyle>
|
|
||||||
</sld:UserStyle>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<sld:UserStyle>
|
|
||||||
<sld:Name>Attribute Filter Styler</sld:Name>
|
|
||||||
<sld:Title>Attribute Filter Styler</sld:Title>
|
|
||||||
<sld:FeatureTypeStyle>
|
|
||||||
<sld:Name>attribute filter type</sld:Name>
|
|
||||||
<sld:Title>attribute filter type</sld:Title>
|
|
||||||
<sld:FeatureTypeName>Feature</sld:FeatureTypeName>
|
|
||||||
<sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
|
|
||||||
<!-- Attribute filters -->
|
|
||||||
<sld:Rule>
|
|
||||||
<sld:Name>rulePropertyIsEqualTo</sld:Name>
|
|
||||||
<sld:Title>rulePropertyIsEqualTo</sld:Title>
|
|
||||||
<sld:Abstract>rulePropertyIsEqualTo</sld:Abstract>
|
|
||||||
<ogc:Filter>
|
|
||||||
<ogc:PropertyIsEqualTo>
|
|
||||||
<ogc:PropertyName>name</ogc:PropertyName>
|
|
||||||
<ogc:Literal>My simple Polygon</ogc:Literal>
|
|
||||||
</ogc:PropertyIsEqualTo>
|
|
||||||
</ogc:Filter>
|
|
||||||
<sld:PolygonSymbolizer>
|
<sld:PolygonSymbolizer>
|
||||||
<sld:Fill>
|
<sld:Fill>
|
||||||
<sld:CssParameter name="fill">
|
<sld:CssParameter name="fill">
|
||||||
<ogc:Literal>#000033</ogc:Literal>
|
<ogc:Literal>black</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="fill-opacity">
|
||||||
|
<ogc:Literal>0.5</ogc:Literal>
|
||||||
</sld:CssParameter>
|
</sld:CssParameter>
|
||||||
</sld:Fill>
|
</sld:Fill>
|
||||||
|
<sld:Stroke>
|
||||||
|
<sld:CssParameter name="stroke">
|
||||||
|
<ogc:Literal>fuchsia</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-opacity">
|
||||||
|
<ogc:Literal>0.5</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-width">
|
||||||
|
<ogc:Literal>5</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-dashoffset">
|
||||||
|
<ogc:Literal>0</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
</sld:Stroke>
|
||||||
</sld:PolygonSymbolizer>
|
</sld:PolygonSymbolizer>
|
||||||
</sld:Rule>
|
</sld:Rule>
|
||||||
</sld:FeatureTypeStyle>
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
|
|
||||||
|
|
||||||
<sld:UserStyle>
|
<sld:UserStyle>
|
||||||
<sld:Name>Attribute Filter Styler</sld:Name>
|
<sld:Name>Attribute Filter Styler</sld:Name>
|
||||||
<sld:Title>Attribute Filter Styler</sld:Title>
|
<sld:Title>Attribute Filter Styler</sld:Title>
|
||||||
@@ -221,38 +237,6 @@
|
|||||||
</sld:FeatureTypeStyle>
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
|
|
||||||
<sld:UserStyle>
|
|
||||||
<sld:Name>Styler Test Not FeatureId</sld:Name>
|
|
||||||
<sld:Title>Styler Test Not FeatureId</sld:Title>
|
|
||||||
<sld:FeatureTypeStyle>
|
|
||||||
<sld:Name>attribute filter type</sld:Name>
|
|
||||||
<sld:Title>attribute filter type</sld:Title>
|
|
||||||
<sld:FeatureTypeName>Feature</sld:FeatureTypeName>
|
|
||||||
<sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
|
|
||||||
<!-- Attribute filters -->
|
|
||||||
<sld:Rule>
|
|
||||||
<sld:Name>ruleNotFeatureId</sld:Name>
|
|
||||||
<sld:Title>ruleNotFeatureId</sld:Title>
|
|
||||||
<sld:Abstract>ruleNotFeatureId</sld:Abstract>
|
|
||||||
<ogc:Filter>
|
|
||||||
<ogc:Not>
|
|
||||||
<ogc:FeatureId fid="tasmania_water_bodies.7" />
|
|
||||||
</ogc:Not>
|
|
||||||
</ogc:Filter>
|
|
||||||
<sld:PolygonSymbolizer>
|
|
||||||
<sld:Fill>
|
|
||||||
<sld:CssParameter name="fill">
|
|
||||||
<ogc:Literal>red</ogc:Literal>
|
|
||||||
</sld:CssParameter>
|
|
||||||
</sld:Fill>
|
|
||||||
</sld:PolygonSymbolizer>
|
|
||||||
</sld:Rule>
|
|
||||||
</sld:FeatureTypeStyle>
|
|
||||||
</sld:UserStyle>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<sld:UserStyle>
|
<sld:UserStyle>
|
||||||
<sld:Name>Styler Test WATER_TYPE</sld:Name>
|
<sld:Name>Styler Test WATER_TYPE</sld:Name>
|
||||||
<sld:Title>Styler Test WATER_TYPE</sld:Title>
|
<sld:Title>Styler Test WATER_TYPE</sld:Title>
|
||||||
@@ -460,7 +444,6 @@
|
|||||||
</sld:FeatureTypeStyle>
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
|
|
||||||
|
|
||||||
<sld:UserStyle>
|
<sld:UserStyle>
|
||||||
<sld:Name>Styler Test PropertyIsBetween</sld:Name>
|
<sld:Name>Styler Test PropertyIsBetween</sld:Name>
|
||||||
<sld:Title>Styler Test PropertyIsBetween</sld:Title>
|
<sld:Title>Styler Test PropertyIsBetween</sld:Title>
|
||||||
@@ -478,10 +461,10 @@
|
|||||||
<ogc:PropertyIsBetween>
|
<ogc:PropertyIsBetween>
|
||||||
<ogc:PropertyName>AREA</ogc:PropertyName>
|
<ogc:PropertyName>AREA</ogc:PropertyName>
|
||||||
<ogc:LowerBoundary>
|
<ogc:LowerBoundary>
|
||||||
<ogc:Literal>1060000000</ogc:Literal>
|
<ogc:Literal>1064866676</ogc:Literal>
|
||||||
</ogc:LowerBoundary>
|
</ogc:LowerBoundary>
|
||||||
<ogc:UpperBoundary>
|
<ogc:UpperBoundary>
|
||||||
<ogc:Literal>1070000000</ogc:Literal>
|
<ogc:Literal>1065512599</ogc:Literal>
|
||||||
</ogc:UpperBoundary>
|
</ogc:UpperBoundary>
|
||||||
</ogc:PropertyIsBetween>
|
</ogc:PropertyIsBetween>
|
||||||
</ogc:Filter>
|
</ogc:Filter>
|
||||||
@@ -496,6 +479,23 @@
|
|||||||
</sld:FeatureTypeStyle>
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
|
|
||||||
|
<sld:UserStyle>
|
||||||
|
<sld:Name>FeatureId</sld:Name>
|
||||||
|
<sld:Title>Styler Test FeatureId</sld:Title>
|
||||||
|
<sld:FeatureTypeStyle>
|
||||||
|
<sld:Rule>
|
||||||
|
<ogc:Filter>
|
||||||
|
<ogc:FeatureId fid="tasmania_water_bodies.4"/>
|
||||||
|
</ogc:Filter>
|
||||||
|
<sld:PolygonSymbolizer>
|
||||||
|
<sld:Fill>
|
||||||
|
<sld:CssParameter name="fill">blue</sld:CssParameter>
|
||||||
|
</sld:Fill>
|
||||||
|
</sld:PolygonSymbolizer>
|
||||||
|
</sld:Rule>
|
||||||
|
</sld:FeatureTypeStyle>
|
||||||
|
</sld:UserStyle>
|
||||||
|
|
||||||
</sld:NamedLayer>
|
</sld:NamedLayer>
|
||||||
|
|
||||||
<sld:NamedLayer>
|
<sld:NamedLayer>
|
||||||
@@ -503,6 +503,7 @@
|
|||||||
<sld:UserStyle>
|
<sld:UserStyle>
|
||||||
<sld:Name>RoadsDefault</sld:Name>
|
<sld:Name>RoadsDefault</sld:Name>
|
||||||
<sld:IsDefault>1</sld:IsDefault>
|
<sld:IsDefault>1</sld:IsDefault>
|
||||||
|
<sld:FeatureTypeStyle>
|
||||||
<sld:Rule>
|
<sld:Rule>
|
||||||
<sld:Name>justAStyler</sld:Name>
|
<sld:Name>justAStyler</sld:Name>
|
||||||
<sld:LineSymbolizer>
|
<sld:LineSymbolizer>
|
||||||
@@ -516,6 +517,7 @@
|
|||||||
</sld:Stroke>
|
</sld:Stroke>
|
||||||
</sld:LineSymbolizer>
|
</sld:LineSymbolizer>
|
||||||
</sld:Rule>
|
</sld:Rule>
|
||||||
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
</sld:NamedLayer>
|
</sld:NamedLayer>
|
||||||
|
|
||||||
@@ -524,6 +526,7 @@
|
|||||||
<sld:UserStyle>
|
<sld:UserStyle>
|
||||||
<sld:Name>DefaultCities</sld:Name>
|
<sld:Name>DefaultCities</sld:Name>
|
||||||
<sld:IsDefault>1</sld:IsDefault>
|
<sld:IsDefault>1</sld:IsDefault>
|
||||||
|
<sld:FeatureTypeStyle>
|
||||||
<sld:Rule>
|
<sld:Rule>
|
||||||
<sld:PointSymbolizer>
|
<sld:PointSymbolizer>
|
||||||
<sld:Graphic>
|
<sld:Graphic>
|
||||||
@@ -531,11 +534,43 @@
|
|||||||
<sld:OnlineResource xlink:href="../img/marker.png" />
|
<sld:OnlineResource xlink:href="../img/marker.png" />
|
||||||
<sld:Format>image/png</sld:Format>
|
<sld:Format>image/png</sld:Format>
|
||||||
</sld:ExternalGraphic>
|
</sld:ExternalGraphic>
|
||||||
<sld:Size>10</sld:Size>
|
|
||||||
<sld:Opacity>0.5</sld:Opacity>
|
<sld:Opacity>0.5</sld:Opacity>
|
||||||
|
<sld:Size>10</sld:Size>
|
||||||
</sld:Graphic>
|
</sld:Graphic>
|
||||||
</sld:PointSymbolizer>
|
</sld:PointSymbolizer>
|
||||||
</sld:Rule>
|
</sld:Rule>
|
||||||
|
</sld:FeatureTypeStyle>
|
||||||
|
</sld:UserStyle>
|
||||||
|
</sld:NamedLayer>
|
||||||
|
|
||||||
|
<sld:NamedLayer>
|
||||||
|
<sld:Name>Land</sld:Name>
|
||||||
|
<sld:UserStyle>
|
||||||
|
<sld:Name>Land Style</sld:Name>
|
||||||
|
<sld:IsDefault>1</sld:IsDefault>
|
||||||
|
<sld:FeatureTypeStyle>
|
||||||
|
<sld:Rule>
|
||||||
|
<sld:PolygonSymbolizer>
|
||||||
|
<sld:Fill>
|
||||||
|
<sld:CssParameter name="fill">#ccffaa</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="fill-opacity">
|
||||||
|
<ogc:Literal>0.5</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
</sld:Fill>
|
||||||
|
<sld:Stroke>
|
||||||
|
<sld:CssParameter name="stroke">
|
||||||
|
<ogc:Literal>#C0C0C0</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-opacity">
|
||||||
|
<ogc:Literal>1</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
<sld:CssParameter name="stroke-width">
|
||||||
|
<ogc:Literal>1</ogc:Literal>
|
||||||
|
</sld:CssParameter>
|
||||||
|
</sld:Stroke>
|
||||||
|
</sld:PolygonSymbolizer>
|
||||||
|
</sld:Rule>
|
||||||
|
</sld:FeatureTypeStyle>
|
||||||
</sld:UserStyle>
|
</sld:UserStyle>
|
||||||
</sld:NamedLayer>
|
</sld:NamedLayer>
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,8 @@
|
|||||||
"OpenLayers/Format/WKT.js",
|
"OpenLayers/Format/WKT.js",
|
||||||
"OpenLayers/Format/OSM.js",
|
"OpenLayers/Format/OSM.js",
|
||||||
"OpenLayers/Format/SLD.js",
|
"OpenLayers/Format/SLD.js",
|
||||||
|
"OpenLayers/Format/SLD/v1.js",
|
||||||
|
"OpenLayers/Format/SLD/v1_0_0.js",
|
||||||
"OpenLayers/Format/Text.js",
|
"OpenLayers/Format/Text.js",
|
||||||
"OpenLayers/Format/JSON.js",
|
"OpenLayers/Format/JSON.js",
|
||||||
"OpenLayers/Format/GeoJSON.js",
|
"OpenLayers/Format/GeoJSON.js",
|
||||||
|
|||||||
@@ -21,59 +21,23 @@
|
|||||||
OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
|
OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIProperty: sldns
|
* APIProperty: defaultVersion
|
||||||
* Namespace used for sld.
|
* {String} Version number to assume if none found. Default is "1.0.0".
|
||||||
*/
|
*/
|
||||||
sldns: "http://www.opengis.net/sld",
|
defaultVersion: "1.0.0",
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIProperty: ogcns
|
* APIProperty: version
|
||||||
* Namespace used for ogc.
|
* {String} Specify a version string if one is known.
|
||||||
*/
|
*/
|
||||||
ogcns: "http://www.opengis.net/ogc",
|
version: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIProperty: gmlns
|
* Property: parser
|
||||||
* Namespace used for gml.
|
* {Object} Instance of the versioned parser. Cached for multiple read and
|
||||||
|
* write calls of the same version.
|
||||||
*/
|
*/
|
||||||
gmlns: "http://www.opengis.net/gml",
|
parser: null,
|
||||||
|
|
||||||
/**
|
|
||||||
* APIProperty: defaultStyle.
|
|
||||||
* {Object}
|
|
||||||
* A simple style, preset with the SLD defaults.
|
|
||||||
*/
|
|
||||||
defaultStyle: {
|
|
||||||
fillColor: "#808080",
|
|
||||||
fillOpacity: 1,
|
|
||||||
strokeColor: "#000000",
|
|
||||||
strokeOpacity: 1,
|
|
||||||
strokeWidth: 1,
|
|
||||||
pointRadius: 6
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Property: withNamedLayer
|
|
||||||
* {Boolean} Option set during <read>. Default is false. If true, the
|
|
||||||
* return from <read> will be a two item array ([styles, namedLayer]):
|
|
||||||
* - styles - {Array(<OpenLayers.Style>)}
|
|
||||||
* - namedLayer - {Object} hash of userStyles, keyed by
|
|
||||||
* sld:NamedLayer/Name, each again keyed by
|
|
||||||
* sld:UserStyle/Name. Each entry of namedLayer is a
|
|
||||||
* StyleMap for a layer, with the userStyle names as style
|
|
||||||
* keys.
|
|
||||||
*/
|
|
||||||
withNamedLayer: false,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APIProperty: overrideDefaultStyleKey
|
|
||||||
* {Boolean} Store styles with key of "default" instead of user style name.
|
|
||||||
* If true, userStyles with sld:IsDefault==1 will be stored with
|
|
||||||
* key "default" instead of the sld:UserStyle/Name in the style map.
|
|
||||||
* Default is true.
|
|
||||||
*/
|
|
||||||
overrideDefaultStyleKey: true,
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor: OpenLayers.Format.SLD
|
* Constructor: OpenLayers.Format.SLD
|
||||||
@@ -88,517 +52,68 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIMethod: read
|
* APIMethod: write
|
||||||
* Read data from a string, and return a list of features.
|
* Write a SLD document given a list of styles.
|
||||||
*
|
*
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* data - {String} or {XMLNode} data to read/parse.
|
* sld - {Object} An object representing the SLD.
|
||||||
* options - {Object} Object that sets optional read configuration values.
|
* options - {Object} Optional configuration object.
|
||||||
* These include <withNamedLayer>, and <overrideDefaultStyleKey>.
|
|
||||||
*
|
*
|
||||||
* Returns:
|
* Returns:
|
||||||
* {Array(<OpenLayers.Style>)} List of styles. If <withNamedLayer> is
|
* {String} An SLD document string.
|
||||||
* true, return will be a two item array where the first item is
|
|
||||||
* a list of styles and the second is the namedLayer object.
|
|
||||||
*/
|
*/
|
||||||
read: function(data, options) {
|
write: function(sld, options) {
|
||||||
|
var version = (options && options.version) ||
|
||||||
|
this.version || this.defaultVersion;
|
||||||
|
if(!this.parser || this.parser.VERSION != version) {
|
||||||
|
var format = OpenLayers.Format.SLD[
|
||||||
|
"v" + version.replace(/\./g, "_")
|
||||||
|
];
|
||||||
|
if(!format) {
|
||||||
|
throw "Can't find a SLD parser for version " +
|
||||||
|
version;
|
||||||
|
}
|
||||||
|
this.parser = new format(this.options);
|
||||||
|
}
|
||||||
|
var root = this.parser.write(sld);
|
||||||
|
return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APIMethod: read
|
||||||
|
* Read and SLD doc and return an object representing the SLD.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* data - {String | DOMElement} Data to read.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* {Object} An object representing the SLD.
|
||||||
|
*/
|
||||||
|
read: function(data) {
|
||||||
if(typeof data == "string") {
|
if(typeof data == "string") {
|
||||||
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
|
data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
|
||||||
}
|
}
|
||||||
|
var root = data.documentElement;
|
||||||
options = options || {};
|
var version = this.version;
|
||||||
OpenLayers.Util.applyDefaults(options, {
|
if(!version) {
|
||||||
withNamedLayer: false,
|
version = root.getAttribute("version");
|
||||||
overrideDefaultStyleKey: true
|
if(!version) {
|
||||||
});
|
version = this.defaultVersion;
|
||||||
|
|
||||||
var userStyles = this.getElementsByTagNameNS(
|
|
||||||
data, this.sldns, "UserStyle"
|
|
||||||
);
|
|
||||||
var result = {};
|
|
||||||
if (userStyles.length > 0) {
|
|
||||||
var namedLayer = {};
|
|
||||||
var styles = new Array(userStyles.length);
|
|
||||||
var styleName, userStyle, style;
|
|
||||||
for (var i=0; i<userStyles.length; i++) {
|
|
||||||
userStyle = userStyles[i];
|
|
||||||
styleName = this.parseProperty(
|
|
||||||
userStyle, this.sldns, "Name"
|
|
||||||
);
|
|
||||||
style = this.parseUserStyle(userStyle, styleName);
|
|
||||||
|
|
||||||
if (options.overrideDefaultStyleKey && style.isDefault == true) {
|
|
||||||
styleName = "default";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!namedLayer[style.layerName]) {
|
|
||||||
namedLayer[style.layerName] = {};
|
|
||||||
}
|
}
|
||||||
namedLayer[style.layerName][styleName] = style;
|
if(!this.parser || this.parser.VERSION != version) {
|
||||||
styles[i] = style;
|
var format = OpenLayers.Format.SLD[
|
||||||
|
"v" + version.replace(/\./g, "_")
|
||||||
|
];
|
||||||
|
if(!format) {
|
||||||
|
throw "Can't find a SLD parser for version " +
|
||||||
|
version;
|
||||||
}
|
}
|
||||||
result = options.withNamedLayer ? [styles, namedLayer] : styles;
|
this.parser = new format(this.options);
|
||||||
}
|
}
|
||||||
return result;
|
var sld = this.parser.read(data);
|
||||||
|
return sld;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: parseUserStyle
|
|
||||||
* parses a sld userStyle for rules
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* xmlNode - {DOMElement} xml node to read the style from
|
|
||||||
* name - {String} name of the style
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* {<OpenLayers.Style>}
|
|
||||||
*/
|
|
||||||
parseUserStyle: function(xmlNode, name) {
|
|
||||||
var userStyle = new OpenLayers.Style(this.defaultStyle, {name: name});
|
|
||||||
|
|
||||||
userStyle.isDefault = (
|
|
||||||
this.parseProperty(xmlNode, this.sldns, "IsDefault") == 1
|
|
||||||
);
|
|
||||||
|
|
||||||
// get the name of the layer if we have a NamedLayer
|
|
||||||
var namedLayerNode = xmlNode.parentNode;
|
|
||||||
var nameNodes = this.getElementsByTagNameNS(
|
|
||||||
namedLayerNode, this.sldns, "Name"
|
|
||||||
);
|
|
||||||
if (namedLayerNode.nodeName.indexOf("NamedLayer") != -1 &&
|
|
||||||
nameNodes &&
|
|
||||||
nameNodes.length > 0 &&
|
|
||||||
nameNodes[0].parentNode == namedLayerNode) {
|
|
||||||
userStyle.layerName = this.getChildValue(nameNodes[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var ruleNodes = this.getElementsByTagNameNS(
|
|
||||||
xmlNode, this.sldns, "Rule"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ruleNodes.length > 0) {
|
|
||||||
var rules = userStyle.rules;
|
|
||||||
var ruleName;
|
|
||||||
for (var i=0; i<ruleNodes.length; i++) {
|
|
||||||
ruleName = this.parseProperty(ruleNodes[i], this.sldns, "Name");
|
|
||||||
rules.push(this.parseRule(ruleNodes[i], ruleName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return userStyle;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: parseRule
|
|
||||||
* This function is the core of the SLD parsing code in OpenLayers.
|
|
||||||
* It creates the rule with its constraints and symbolizers.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* xmlNode - {<DOMElement>}
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* {Object} Hash of rule properties
|
|
||||||
*/
|
|
||||||
parseRule: function(xmlNode, name) {
|
|
||||||
|
|
||||||
// FILTERS
|
|
||||||
|
|
||||||
var filter = this.getElementsByTagNameNS(xmlNode, this.ogcns, "Filter");
|
|
||||||
if (filter && filter.length > 0) {
|
|
||||||
var rule = this.parseFilter(filter[0]);
|
|
||||||
} else {
|
|
||||||
// start with an empty rule that always applies
|
|
||||||
var rule = new OpenLayers.Rule();
|
|
||||||
// and check if the rule is an ElseFilter
|
|
||||||
var elseFilter = this.getElementsByTagNameNS(xmlNode, this.ogcns,
|
|
||||||
"ElseFilter");
|
|
||||||
if (elseFilter && elseFilter.length > 0) {
|
|
||||||
rule.elseFilter = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rule.name = name;
|
|
||||||
|
|
||||||
// SCALE DENOMINATORS
|
|
||||||
|
|
||||||
// MinScaleDenominator
|
|
||||||
var minScale = this.getElementsByTagNameNS(
|
|
||||||
xmlNode, this.sldns, "MinScaleDenominator"
|
|
||||||
);
|
|
||||||
if (minScale && minScale.length > 0) {
|
|
||||||
rule.minScaleDenominator =
|
|
||||||
parseFloat(this.getChildValue(minScale[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxScaleDenominator
|
|
||||||
var maxScale = this.getElementsByTagNameNS(
|
|
||||||
xmlNode, this.sldns, "MaxScaleDenominator"
|
|
||||||
);
|
|
||||||
if (maxScale && maxScale.length > 0) {
|
|
||||||
rule.maxScaleDenominator =
|
|
||||||
parseFloat(this.getChildValue(maxScale[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// STYLES
|
|
||||||
|
|
||||||
// walk through all symbolizers
|
|
||||||
var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
|
|
||||||
for (var s=0; s<prefixes.length; s++) {
|
|
||||||
|
|
||||||
// symbolizer type
|
|
||||||
var symbolizer = this.getElementsByTagNameNS(
|
|
||||||
xmlNode, this.sldns, prefixes[s]+"Symbolizer"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (symbolizer && symbolizer.length > 0) {
|
|
||||||
|
|
||||||
var style = {};
|
|
||||||
|
|
||||||
// externalGraphic
|
|
||||||
var graphic = this.getElementsByTagNameNS(
|
|
||||||
symbolizer[0], this.sldns, "Graphic"
|
|
||||||
);
|
|
||||||
if (graphic && graphic.length > 0) {
|
|
||||||
style.externalGraphic = this.parseProperty(
|
|
||||||
graphic[0], this.sldns, "OnlineResource", "xlink:href"
|
|
||||||
);
|
|
||||||
style.pointRadius = this.parseProperty(
|
|
||||||
graphic[0], this.sldns, "Size"
|
|
||||||
);
|
|
||||||
style.graphicOpacity = this.parseProperty(
|
|
||||||
graphic[0], this.sldns, "Opacity"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill
|
|
||||||
var fill = this.getElementsByTagNameNS(
|
|
||||||
symbolizer[0], this.sldns, "Fill"
|
|
||||||
);
|
|
||||||
if (fill && fill.length > 0) {
|
|
||||||
style.fillColor = this.parseProperty(
|
|
||||||
fill[0], this.sldns, "CssParameter", "name", "fill"
|
|
||||||
);
|
|
||||||
style.fillOpacity = this.parseProperty(
|
|
||||||
fill[0], this.sldns, "CssParameter",
|
|
||||||
"name", "fill-opacity"
|
|
||||||
) || 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// stroke
|
|
||||||
var stroke = this.getElementsByTagNameNS(
|
|
||||||
symbolizer[0], this.sldns, "Stroke"
|
|
||||||
);
|
|
||||||
if (stroke && stroke.length > 0) {
|
|
||||||
style.strokeColor = this.parseProperty(
|
|
||||||
stroke[0], this.sldns, "CssParameter", "name", "stroke"
|
|
||||||
);
|
|
||||||
style.strokeOpacity = this.parseProperty(
|
|
||||||
stroke[0], this.sldns, "CssParameter",
|
|
||||||
"name", "stroke-opacity"
|
|
||||||
) || 1;
|
|
||||||
style.strokeWidth = this.parseProperty(
|
|
||||||
stroke[0], this.sldns, "CssParameter",
|
|
||||||
"name", "stroke-width"
|
|
||||||
);
|
|
||||||
style.strokeLinecap = this.parseProperty(
|
|
||||||
stroke[0], this.sldns, "CssParameter",
|
|
||||||
"name", "stroke-linecap"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the [point|line|polygon]Symbolizer property of the rule
|
|
||||||
rule.symbolizer[prefixes[s]] = style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: parseFilter
|
|
||||||
* Parses ogc fiters.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* xmlNode - {<DOMElement>}
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* {<OpenLayers.Rule>} rule representing the filter
|
|
||||||
*/
|
|
||||||
parseFilter: function(xmlNode) {
|
|
||||||
// ogc:FeatureId filter
|
|
||||||
var filter = this.getNodeOrChildrenByTagName(xmlNode, "FeatureId");
|
|
||||||
if (filter) {
|
|
||||||
var rule = new OpenLayers.Rule.FeatureId();
|
|
||||||
for (var i=0; i<filter.length; i++) {
|
|
||||||
rule.fids.push(filter[i].getAttribute("fid"));
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ogc:And filter
|
|
||||||
filter = this.getNodeOrChildrenByTagName(xmlNode, "And");
|
|
||||||
if (filter) {
|
|
||||||
var rule = new OpenLayers.Rule.Logical(
|
|
||||||
{type: OpenLayers.Rule.Logical.AND});
|
|
||||||
var filters = filter[0].childNodes;
|
|
||||||
for (var i=0; i<filters.length; i++) {
|
|
||||||
if (filters[i].nodeType == 1) {
|
|
||||||
rule.rules.push(this.parseFilter(filters[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ogc:Or filter
|
|
||||||
filter = this.getNodeOrChildrenByTagName(xmlNode, "Or");
|
|
||||||
if (filter) {
|
|
||||||
var rule = new OpenLayers.Rule.Logical(
|
|
||||||
{type: OpenLayers.Rule.Logical.OR})
|
|
||||||
var filters = filter[0].childNodes;
|
|
||||||
for (var i=0; i<filters.length; i++) {
|
|
||||||
if (filters[i].nodeType == 1) {
|
|
||||||
rule.rules.push(this.parseFilter(filters[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ogc:Not filter
|
|
||||||
filter = this.getNodeOrChildrenByTagName(xmlNode, "Not");
|
|
||||||
if (filter) {
|
|
||||||
var rule = new OpenLayers.Rule.Logical(
|
|
||||||
{type: OpenLayers.Rule.Logical.NOT});
|
|
||||||
var filters = filter[0].childNodes;
|
|
||||||
for (var i=0; i<filters.length; i++) {
|
|
||||||
if (filters[i].nodeType == 1) {
|
|
||||||
rule.rules.push(this.parseFilter(filters[i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comparison filters
|
|
||||||
for (var type in this.TYPES) {
|
|
||||||
var filter = this.getNodeOrChildrenByTagName(xmlNode, type);
|
|
||||||
if (filter) {
|
|
||||||
filter = filter[0];
|
|
||||||
var rule = new OpenLayers.Rule.Comparison({
|
|
||||||
type: OpenLayers.Rule.Comparison[this.TYPES[type]],
|
|
||||||
property: this.parseProperty(
|
|
||||||
filter, this.ogcns, "PropertyName")});
|
|
||||||
// ogc:PropertyIsBetween
|
|
||||||
if (this.TYPES[type] == "BETWEEN") {
|
|
||||||
rule.lowerBoundary = this.parseProperty(
|
|
||||||
filter, this.ogcns, "LowerBoundary");
|
|
||||||
rule.upperBoudary = this.parseProperty(
|
|
||||||
filter, this.ogcns, "UpperBoundary");
|
|
||||||
} else {
|
|
||||||
rule.value = this.parseProperty(
|
|
||||||
filter, this.ogcns, "Literal");
|
|
||||||
// ogc:PropertyIsLike
|
|
||||||
if (this.TYPES[type] == "LIKE") {
|
|
||||||
var wildCard = filter.getAttribute("wildCard");
|
|
||||||
var singleChar = filter.getAttribute("singleChar");
|
|
||||||
var escape = filter.getAttribute("escape");
|
|
||||||
rule.value2regex(wildCard, singleChar, escape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rule;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we get here, the filter was empty
|
|
||||||
return new OpenLayers.Rule();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: getNodeOrChildrenByTagName
|
|
||||||
* Convenience method to get a node or its child nodes, but only
|
|
||||||
* those matching a tag name.
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* {Array(<DOMElement>)} or null if no matching content is found
|
|
||||||
*/
|
|
||||||
getNodeOrChildrenByTagName: function(xmlNode, tagName) {
|
|
||||||
var nodeName = (xmlNode.prefix) ?
|
|
||||||
xmlNode.nodeName.split(":")[1] :
|
|
||||||
xmlNode.nodeName;
|
|
||||||
|
|
||||||
if (nodeName == tagName) {
|
|
||||||
return [xmlNode];
|
|
||||||
} else {
|
|
||||||
var nodelist = this.getElementsByTagNameNS(
|
|
||||||
xmlNode, this.ogcns, tagName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a new list which only contains matching child nodes
|
|
||||||
if (nodelist.length > 0) {
|
|
||||||
var node;
|
|
||||||
var list = [];
|
|
||||||
for (var i=0; i<nodelist.length; i++) {
|
|
||||||
node = nodelist[i];
|
|
||||||
if (node.parentNode == xmlNode) {
|
|
||||||
list.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list.length > 0 ? list : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: parseProperty
|
|
||||||
* Convenience method to parse the different kinds of properties
|
|
||||||
* found in the sld and ogc namespace.
|
|
||||||
*
|
|
||||||
* Parses an ogc node that can either contain a value directly,
|
|
||||||
* or inside a <Literal> property. The parsing can also be limited
|
|
||||||
* to nodes with certain attribute names and/or values.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* xmlNode - {<DOMElement>}
|
|
||||||
* namespace - {String} namespace of the node to find
|
|
||||||
* propertyName - {String} name of the property to parse
|
|
||||||
* attributeName - {String} optional name of the property to match
|
|
||||||
* attributeValue - {String} optional value of the specified attribute
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* {String} The value for the requested property.
|
|
||||||
*/
|
|
||||||
parseProperty: function(xmlNode, namespace, propertyName, attributeName,
|
|
||||||
attributeValue) {
|
|
||||||
var result = null;
|
|
||||||
var propertyNodeList = this.getElementsByTagNameNS(
|
|
||||||
xmlNode, namespace, propertyName);
|
|
||||||
|
|
||||||
if (propertyNodeList && propertyNodeList.length > 0) {
|
|
||||||
var propertyNode = attributeName ?
|
|
||||||
this.getNodeWithAttribute(propertyNodeList,
|
|
||||||
attributeName) :
|
|
||||||
propertyNodeList[0];
|
|
||||||
|
|
||||||
// strip namespace from attribute name for Opera browsers
|
|
||||||
if (window.opera && attributeName) {
|
|
||||||
var nsDelimiterPos = attributeName.indexOf(":");
|
|
||||||
if (nsDelimiterPos != -1) {
|
|
||||||
attributeName = attributeName.substring(++nsDelimiterPos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the property value from the node matching attributeName
|
|
||||||
// and attributeValue, eg.:
|
|
||||||
// <CssParameter name="stroke">
|
|
||||||
// <ogc:Literal>red</ogc:Literal>
|
|
||||||
// </CssParameter>
|
|
||||||
// or:
|
|
||||||
// <CssParameter name="stroke">red</CssParameter>
|
|
||||||
if (attributeName && attributeValue) {
|
|
||||||
propertyNode = this.getNodeWithAttribute(propertyNodeList,
|
|
||||||
attributeName, attributeValue);
|
|
||||||
result = this.parseParameter(propertyNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the attribute value and use it as result, eg.:
|
|
||||||
// <sld:OnlineResource xlink:href="../img/marker.png"/>
|
|
||||||
if (attributeName && !attributeValue) {
|
|
||||||
var propertyNode = this.getNodeWithAttribute(propertyNodeList,
|
|
||||||
attributeName);
|
|
||||||
result = propertyNode.getAttribute(attributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the property value directly or from an ogc:propertyName,
|
|
||||||
// ogc:Literal or any other property at the level of the property
|
|
||||||
// node, eg.:
|
|
||||||
// <sld:Opacity>0.5</sld:Opacity>
|
|
||||||
if (!attributeName) {
|
|
||||||
var result = this.parseParameter(propertyNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjust the result to be a trimmed string or a number
|
|
||||||
if (result) {
|
|
||||||
result = OpenLayers.String.trim(result);
|
|
||||||
if (!isNaN(result)) {
|
|
||||||
result = parseFloat(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: parseParameter
|
|
||||||
* parses a property for propertyNames, Literals and textContent and
|
|
||||||
* creates the according value string.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* xmlNode - {<DOMElement>}
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* {String} a string holding a value suitable for OpenLayers.Style.value
|
|
||||||
*/
|
|
||||||
parseParameter: function(xmlNode) {
|
|
||||||
if (!xmlNode) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var childNodes = xmlNode.childNodes;
|
|
||||||
if (!childNodes) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = new Array(childNodes.length);
|
|
||||||
for (var i=0; i<childNodes.length; i++) {
|
|
||||||
if (childNodes[i].nodeName.indexOf("Literal") != -1) {
|
|
||||||
value[i] = this.getChildValue(childNodes[i]);
|
|
||||||
} else
|
|
||||||
if (childNodes[i].nodeName.indexOf("propertyName") != -1) {
|
|
||||||
value[i] = "${" + this.getChildValue(childNodes[i]) + "}";
|
|
||||||
} else
|
|
||||||
if (childNodes[i].nodeType == 3) {
|
|
||||||
value[i] = childNodes[i].text || childNodes[i].textContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value.join("");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method: getNodeWithAttribute
|
|
||||||
* Walks through a list of xml nodes and returns the fist node that has an
|
|
||||||
* attribute with the name and optional value specified.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* xmlNodeList - {Array(<DOMElement>)} list to search
|
|
||||||
* attributeName - {String} name of the attribute to match
|
|
||||||
* attributeValue - {String} optional value of the attribute
|
|
||||||
*/
|
|
||||||
getNodeWithAttribute: function(xmlNodeList, attributeName, attributeValue) {
|
|
||||||
for (var i=0; i<xmlNodeList.length; i++) {
|
|
||||||
var currentAttributeValue =
|
|
||||||
xmlNodeList[i].getAttribute(attributeName);
|
|
||||||
if (currentAttributeValue) {
|
|
||||||
if (!attributeValue) {
|
|
||||||
return xmlNodeList[i];
|
|
||||||
} else if (currentAttributeValue == attributeValue) {
|
|
||||||
return xmlNodeList[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constant: TYPES
|
|
||||||
* {Object} Mapping between SLD rule names and rule type constants.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
TYPES: {'PropertyIsEqualTo': 'EQUAL_TO',
|
|
||||||
'PropertyIsNotEqualTo': 'NOT_EQUAL_TO',
|
|
||||||
'PropertyIsLessThan': 'LESS_THAN',
|
|
||||||
'PropertyIsGreaterThan': 'GREATER_THAN',
|
|
||||||
'PropertyIsLessThanOrEqualTo': 'LESS_THAN_OR_EQUAL_TO',
|
|
||||||
'PropertyIsGreaterThanOrEqualTo': 'GREATER_THAN_OR_EQUAL_TO',
|
|
||||||
'PropertyIsBetween': 'BETWEEN',
|
|
||||||
'PropertyIsLike': 'LIKE'},
|
|
||||||
|
|
||||||
CLASS_NAME: "OpenLayers.Format.SLD"
|
CLASS_NAME: "OpenLayers.Format.SLD"
|
||||||
});
|
});
|
||||||
|
|||||||
1186
lib/OpenLayers/Format/SLD/v1.js
Normal file
1186
lib/OpenLayers/Format/SLD/v1.js
Normal file
File diff suppressed because it is too large
Load Diff
49
lib/OpenLayers/Format/SLD/v1_0_0.js
Normal file
49
lib/OpenLayers/Format/SLD/v1_0_0.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
|
||||||
|
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
|
||||||
|
* full text of the license. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires OpenLayers/Format/SLD/v1.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class: OpenLayers.Format.SLD.v1_0_0
|
||||||
|
* Write SLD version 1.0.0.
|
||||||
|
*
|
||||||
|
* Inherits from:
|
||||||
|
* - <OpenLayers.Format.SLD.v1>
|
||||||
|
*/
|
||||||
|
OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class(
|
||||||
|
OpenLayers.Format.SLD.v1, {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constant: VERSION
|
||||||
|
* {String} 1.0.0
|
||||||
|
*/
|
||||||
|
VERSION: "1.0.0",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: schemaLocation
|
||||||
|
* {String} http://www.opengis.net/sld
|
||||||
|
* http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd
|
||||||
|
*/
|
||||||
|
schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor: OpenLayers.Format.SLD.v1_0_0
|
||||||
|
* Instances of this class are not created directly. Use the
|
||||||
|
* <OpenLayers.Format.SLD> constructor instead.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* options - {Object} An optional object whose properties will be set on
|
||||||
|
* this instance.
|
||||||
|
*/
|
||||||
|
initialize: function(options) {
|
||||||
|
OpenLayers.Format.SLD.v1.prototype.initialize.apply(
|
||||||
|
this, [options]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0"
|
||||||
|
|
||||||
|
});
|
||||||
@@ -14,12 +14,30 @@
|
|||||||
*/
|
*/
|
||||||
OpenLayers.Rule = OpenLayers.Class({
|
OpenLayers.Rule = OpenLayers.Class({
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: id
|
||||||
|
* {String} A unique id for this session.
|
||||||
|
*/
|
||||||
|
id: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIProperty: name
|
* APIProperty: name
|
||||||
* {String} name of this rule
|
* {String} name of this rule
|
||||||
*/
|
*/
|
||||||
name: 'default',
|
name: 'default',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: title
|
||||||
|
* {String} Title of this rule (set if included in SLD)
|
||||||
|
*/
|
||||||
|
title: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: description
|
||||||
|
* {String} Description of this rule (set if abstract is included in SLD)
|
||||||
|
*/
|
||||||
|
description: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property: context
|
* Property: context
|
||||||
* {Object} An optional object with properties that the rule should be
|
* {Object} An optional object with properties that the rule should be
|
||||||
@@ -73,6 +91,7 @@ OpenLayers.Rule = OpenLayers.Class({
|
|||||||
* {<OpenLayers.Rule>}
|
* {<OpenLayers.Rule>}
|
||||||
*/
|
*/
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
|
this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
|
||||||
this.symbolizer = {};
|
this.symbolizer = {};
|
||||||
|
|
||||||
OpenLayers.Util.extend(this, options);
|
OpenLayers.Util.extend(this, options);
|
||||||
|
|||||||
@@ -107,9 +107,9 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
|
|||||||
|
|
||||||
case OpenLayers.Rule.Comparison.BETWEEN:
|
case OpenLayers.Rule.Comparison.BETWEEN:
|
||||||
var result =
|
var result =
|
||||||
context[this.property] > this.lowerBoundary;
|
context[this.property] >= this.lowerBoundary;
|
||||||
result = result &&
|
result = result &&
|
||||||
context[this.property] < this.upperBoundary;
|
context[this.property] <= this.upperBoundary;
|
||||||
return result;
|
return result;
|
||||||
case OpenLayers.Rule.Comparison.LIKE:
|
case OpenLayers.Rule.Comparison.LIKE:
|
||||||
var regexp = new RegExp(this.value,
|
var regexp = new RegExp(this.value,
|
||||||
@@ -163,6 +163,41 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
|
|||||||
return this.value;
|
return this.value;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method: regex2value
|
||||||
|
* Convert the value of this rule from a regular expression string into an
|
||||||
|
* ogc literal string using a wildCard of *, a singleChar of ., and an
|
||||||
|
* escape of !. Leaves the <value> property unmodified.
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* {String} A string value.
|
||||||
|
*/
|
||||||
|
regex2value: function() {
|
||||||
|
|
||||||
|
var value = this.value;
|
||||||
|
|
||||||
|
// replace ! with !!
|
||||||
|
value = value.replace(/!/g, "!!");
|
||||||
|
|
||||||
|
// replace \. with !. (watching out for \\.)
|
||||||
|
value = value.replace(/(\\)?\\\./g, function($0, $1) {
|
||||||
|
return $1 ? $0 : "!.";
|
||||||
|
});
|
||||||
|
|
||||||
|
// replace \* with #* (watching out for \\*)
|
||||||
|
value = value.replace(/(\\)?\\\*/g, function($0, $1) {
|
||||||
|
return $1 ? $0 : "!*";
|
||||||
|
});
|
||||||
|
|
||||||
|
// replace \\ with \
|
||||||
|
value = value.replace(/\\\\/g, "\\");
|
||||||
|
|
||||||
|
// convert .* to * (the sequence #.* is not allowed)
|
||||||
|
value = value.replace(/\.\*/g, "*");
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function: binaryCompare
|
* Function: binaryCompare
|
||||||
* Compares a feature property to a rule value
|
* Compares a feature property to a rule value
|
||||||
|
|||||||
@@ -21,6 +21,18 @@ OpenLayers.Style = OpenLayers.Class({
|
|||||||
*/
|
*/
|
||||||
name: null,
|
name: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: title
|
||||||
|
* {String} Title of this style (set if included in SLD)
|
||||||
|
*/
|
||||||
|
title: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Property: description
|
||||||
|
* {String} Description of this style (set if abstract is included in SLD)
|
||||||
|
*/
|
||||||
|
description: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIProperty: layerName
|
* APIProperty: layerName
|
||||||
* {<String>} name of the layer that this style belongs to, usually
|
* {<String>} name of the layer that this style belongs to, usually
|
||||||
|
|||||||
@@ -17,17 +17,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function test_Format_SLD_read(t) {
|
function test_Format_SLD_read(t) {
|
||||||
t.plan(5);
|
t.plan(4);
|
||||||
var styles = new OpenLayers.Format.SLD().read(this.test_content,
|
var sld = new OpenLayers.Format.SLD().read(this.test_content);
|
||||||
{withNamedLayer: true});
|
|
||||||
|
|
||||||
var testLayer = styles[1].TestLayer;
|
var testLayer = sld.namedLayers["TestLayer"];
|
||||||
|
var userStyles = testLayer.userStyles;
|
||||||
|
|
||||||
t.ok(testLayer.foo != undefined, "SLD correctly reads a UserStyle named \"foo\"");
|
t.eq(userStyles[0].name, "foo", "SLD correctly reads a UserStyle named 'foo'");
|
||||||
t.eq(testLayer.foo.rules.length, 1, "The number of rules for the UserStyle is correct");
|
t.eq(userStyles[0].rules.length, 1, "The number of rules for the UserStyle is correct");
|
||||||
t.eq(testLayer.foo.rules[0].name, "bar", "The first rule's name is \"bar\"");
|
t.eq(userStyles[0].rules[0].name, "bar", "The first rule's name is 'bar'");
|
||||||
t.eq(testLayer.foo.rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct");
|
t.eq(userStyles[0].rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct");
|
||||||
t.eq(testLayer.foo.name, styles[0][0].name, "The content hash of the Format contains the correct rules.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -37,26 +37,50 @@
|
|||||||
t.eq(rule.value, ".*b.r\\%\\..*", "Regular expression with different wildcard and escape chars generated correctly.");
|
t.eq(rule.value, ".*b.r\\%\\..*", "Regular expression with different wildcard and escape chars generated correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_regex2value(t) {
|
||||||
|
t.plan(8);
|
||||||
|
|
||||||
|
function r2v(regex) {
|
||||||
|
return OpenLayers.Rule.Comparison.prototype.regex2value.call(
|
||||||
|
{value: regex}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
t.eq(r2v("foo"), "foo", "doesn't change string without special chars");
|
||||||
|
t.eq(r2v("foo.*foo"), "foo*foo", "wildCard replaced");
|
||||||
|
t.eq(r2v("foo.foo"), "foo.foo", "singleChar replaced");
|
||||||
|
t.eq(r2v("foo\\\\foo"), "foo\\foo", "escape removed");
|
||||||
|
t.eq(r2v("foo!foo"), "foo!!foo", "escapes !");
|
||||||
|
t.eq(r2v("foo\\*foo"), "foo!*foo", "replaces escape on *");
|
||||||
|
t.eq(r2v("foo\\.foo"), "foo!.foo", "replaces escape on .");
|
||||||
|
t.eq(r2v("foo\\\\.foo"), "foo\\.foo", "unescapes only \\ before .");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function test_Comparison_evaluate(t) {
|
function test_Comparison_evaluate(t) {
|
||||||
t.plan(4);
|
t.plan(5);
|
||||||
|
|
||||||
var rule = new OpenLayers.Rule.Comparison({
|
var rule = new OpenLayers.Rule.Comparison({
|
||||||
property: "area",
|
property: "area",
|
||||||
lowerBoundary: 1000,
|
lowerBoundary: 1000,
|
||||||
upperBoundary: 5000,
|
upperBoundary: 4999,
|
||||||
type: OpenLayers.Rule.Comparison.BETWEEN});
|
type: OpenLayers.Rule.Comparison.BETWEEN});
|
||||||
|
|
||||||
var features = [
|
var features = [
|
||||||
new OpenLayers.Feature.Vector(null, {
|
new OpenLayers.Feature.Vector(null, {
|
||||||
area: 2000}),
|
area: 999}),
|
||||||
new OpenLayers.Feature.Vector(null, {
|
new OpenLayers.Feature.Vector(null, {
|
||||||
area: 6000}),
|
area: 1000}),
|
||||||
new OpenLayers.Feature.Vector(null, {
|
new OpenLayers.Feature.Vector(null, {
|
||||||
area: 4999})];
|
area: 4999}),
|
||||||
|
new OpenLayers.Feature.Vector(null, {
|
||||||
|
area: 5000})];
|
||||||
|
// PropertyIsBetween filter: lower and upper boundary are inclusive
|
||||||
var ruleResults = {
|
var ruleResults = {
|
||||||
0: true,
|
0: false,
|
||||||
1: false,
|
1: true,
|
||||||
2: true};
|
2: true,
|
||||||
|
3: false};
|
||||||
for (var i in ruleResults) {
|
for (var i in ruleResults) {
|
||||||
var result = rule.evaluate(features[i]);
|
var result = rule.evaluate(features[i]);
|
||||||
t.eq(result, ruleResults[i], "feature "+i+
|
t.eq(result, ruleResults[i], "feature "+i+
|
||||||
|
|||||||
Reference in New Issue
Block a user