Заключительная часть по SLD, которая посвящена символизации полигонов. Рекомендую прочитать символизацию точек и символизацию линий, прежде чем переходить к полигонам, так как данная статья будет опираться на предыдущий материал.
Предполагается, что все примеры кода, описанные в этой статье, находятся внутри следующей XML:
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/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></Name>
<UserStyle>
<Title>Basic polygon style</Title>
<FeatureTypeStyle>
<Rule>
<Title>Здесь идёт заголовок легенды</Title>
<!-- ТЕЛО СТИЛЯ ИДЁТ ЗДЕСЬ -->
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
PolygonSymbolizer
Внутри PolygonSymbolizer
допустимо использование следующих тэгов:
Geometry
— опциональный, определяет источник геометрииFill
— опциональный, отвечает за заливку полигонаStroke
— опциональный, стилизация границы (контура) полигона
Geometry
через определённые трансформации изменяет исходную геометрию и применяет стилизацию к той геометрии, которая получилась на выходе.
Stroke
определяет границу полигона. В нём можно указать всё то, что мы рассматривали в символизации линий, раздел Stroke.
Fill
задаёт заливку полигона. Может содержать два необязательных тэга — GraphicFill
и CssParameter
. В GraphicFill
может содержаться тэг Graphic
со всем содержимым, рассмотренным в символизации точек. CssParameter
может быть всего два со следующими значениями тэга name
:
fill
— цвет заливки в формате#RRGGBB
(по умолчанию#808080
)fill-opacity
— непрозрачность, значение от 0 (полностью прозрачный) до 1 (полностью непрозрачный). Значение по умолчанию — 1
Вот, в целом, и вся символизация полигонов, так как основная часть тэгов уже была описана в двух предыдущих статьях. Для закрепления перейдём к примерам.
Примеры
Мы снова начнём со стилей попроще и постепенно будем наращивать сложность символизации.
Базовый стиль

Базовый стиль, который GeoServer применяет ко всем слоям с полигонами, выглядит следующим образом:
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">#AAAAAA</CssParameter>
<CssParameter name="fill-opacity">1</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">#000000</CssParameter>
<CssParameter name="stroke-width">1</CssParameter>
</Stroke>
</PolygonSymbolizer>
Для заливки используется серый цвет, для границы — чёрная линия толщиной в 1 пиксель, полностью непрозрачная. В целом, fill
и fill-opacity
— это всё, что появилось нового в символизации полигонов (ну почти всё, есть ещё несколько вещей, о которых чуть позже). Для границы используется уже знакомая символизация линий, для заливки — символизация точек. Поэтому рассмотрим разные варианты комбинации уже знакомых приёмов.
Примеры из топографии
Чтобы не придумывать самому себе задания, я снова обратился к топографическим знакам — здесь и стилизация попадается довольно сложная, да и кому-нибудь может оказаться полезным готовый стиль. Например, возьмём кустарник:

Это один из самых простых стилей — просто нужно выявить повторяющийся фрагмент. Этим фрагментом будет «x», так как если повторять этот символ и по вертикали, и по горизонтали, мы получим косую штриховку. Аналогично при использовании «+» мы получим прямую штриховку:
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>shape://times</WellKnownName>
<Stroke />
</Mark>
</Graphic>
</GraphicFill>
</Fill>
<Stroke>
<CssParameter name="stroke-dasharray">10 2 2 2</CssParameter>
</Stroke>
</PolygonSymbolizer>
Обратите внимание, что в WellKnownName
был использован shape://times
вместо x
. В символизации точек были значки с отступом и без отступа. Дело в том, что если использовать значки «x» без отступа — они сольются и получится штриховка. Если же использовать «x» с отступом, мы получим отдельностоящие символы «x». Далее в стиле идёт пустой тэг Stroke
— снова повторюсь, что есть большая разница между пустым тэгом и отсутствием тэга. Пустой тэг говорит о том, что все параметры идут по умолчанию и символы отрисуются чёрным цветом толщиной в 1 пиксель. Если тэг будет отсутствовать — символы не прорисуются вовсе и мы не получим заливку, так как GeoServer посчитает, что для символов заливки не задана символизация. Помимо заливки мы указали штрихпунктирную границу — просто потому что мы так можем. Рассмотрим ещё один несложный пример — фруктовые сады.

Сразу видно, что нужно использовать знак circle
для заливки кружочками. Но если просто залить кружочками, между ними не будет отступов и они все прилипнут друг к другу. Для решения этой проблемы воспользуемся тэгом VendorOption
:
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Stroke />
</Mark>
<Size>8</Size>
</Graphic>
</GraphicFill>
</Fill>
<VendorOption name="graphic-margin">6</VendorOption>
</PolygonSymbolizer>
Мы сделали заливку окружностями размером 8 пикселей с отступом в 6 пикселей. При этом мы не указали символизацию границ полигонов — из-за этого все штаты слились друг с другом в одну область. Что касается VendorOption
— эти теги не являются частью спецификации SLD 1.0 и добавлены только в GeoServer’е. Ими можно пользоваться, когда не хватает возможностей SLD, но имейте ввиду, что при переносе стилей в другое окружение VendorOption
будем вести себя непредсказуемо.
Рассмотрим ещё один пример — залежи:

В качестве значка подойдёт открытая стрелка, повёрнутая на 90 градусов по часовой стрелке. Плюс сделаем небольшой отступ размером 5 пикселей, чтобы значки не сливались друг с другом:
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>shape://oarrow</WellKnownName>
<Stroke />
</Mark>
<Size>10</Size>
<Rotation>90</Rotation>
</Graphic>
</GraphicFill>
</Fill>
<Stroke />
<VendorOption name="graphic-margin">5</VendorOption>
</PolygonSymbolizer>
Таким несложным стилем мы получили наши залежи. Или нет?..
Более сложные примеры из топографии
На самом деле нет, залежи выглядят не так. Каждый новый ряд идёт со смещением относительно предыдущего. Ещё я от себя добавил зелёную заливку, чтобы картинка не была такой монохромной:

Как воспроизвести такое смещение? Ответ — всё тем же VendorOption
и graphic-margin
. Ранее мы задавали один отступ во все стороны. В случае «недозалежей», рассмотренных чуть выше, значок был размером 10 пикселей плюс с каждой стороны был отступ 5 пикселей. Выходило, что между значками был отступ 10 пикселей — сначала выделяется 10 пикселей на рисование самого значка, потом идёт отступ справа 5 пикселей, затем ещё отступ слева 5 пикселей уже у следующего значка, только затем рисовался следующий значок. Та же схема для вертикали — отступ 5 пикселей снизу одного значка суммируется с отступом 5 пикселей сверху следующего значка и выходит 10 пикселей расстояния между двумя соседними значками. Но для graphic-margin
можно задать отступ для каждой стороны — последовательно для верхней, правой, нижней и левой сторон. В итоге нам понадобится три символизации — первая для заливки цветом (обязательно первой, иначе она зальёт и все знаки, идущие до неё), вторая для одного узора ложбин и третья для смещённого узора ложбин:
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">#3E8311</CssParameter>
</Fill>
</PolygonSymbolizer>
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>shape://oarrow</WellKnownName>
<Stroke />
</Mark>
<Size>10</Size>
<Rotation>90</Rotation>
</Graphic>
</GraphicFill>
</Fill>
<Stroke />
<VendorOption name="graphic-margin">0 10 10 0</VendorOption>
</PolygonSymbolizer>
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>shape://oarrow</WellKnownName>
<Stroke />
</Mark>
<Size>10</Size>
<Rotation>90</Rotation>
</Graphic>
</GraphicFill>
</Fill>
<Stroke />
<VendorOption name="graphic-margin">10 0 0 10</VendorOption>
</PolygonSymbolizer>
Со сплошной заливкой всё понятно (первый PolygonSymbolizer
). Дальше идёт основной узор. Он прижат к левому верхнему углу, так как у него отступ сверху и слева равен нулю. В то же время отступ справа и снизу равен 10 пикселям, что позволяет сохранить отступ в 10 пикселей между всеми значками (второй PolygonSymbolizer
). Далее идёт смещённый узор, который отстоит от правого верхнего угла на 10 пикселей по вертикали и 10 пикселей по горизонтали, за счёт чего и достигается требуемый эффект (третий PolygonSymbolizer
).
Возьмём ещё один пример — кустарники внутри земляного обрыва. Не уверен, что такое бывает, но для примера подойдёт отлично:

Земляной обрыв сделаем через символизацию линий. Заливка идёт по тому же принципу, что и в залежах — то есть следующий ряд со смещением, только значок другой. Причём, этот значок довольно трудно воспроизвести, используя стандартные фигуры. Гораздо проще использовать подготовленную картинку:
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<ExternalGraphic>
<OnlineResource xlink:type="simple" xlink:href="kust.png" />
<Format>image/png</Format>
</ExternalGraphic>
<Size>32</Size>
</Graphic>
</GraphicFill>
</Fill>
<Stroke />
</PolygonSymbolizer>
<LineSymbolizer>
<Stroke />
</LineSymbolizer>
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>shape://vertline</WellKnownName>
<Stroke />
</Mark>
<Size>6</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">6 8</CssParameter>
</Stroke>
<PerpendicularOffset>-3</PerpendicularOffset>
</LineSymbolizer>
Так как мы всё равно используем картинку, можно пойти на небольшую хитрость для оптимизации — а именно использовать сразу два кустарника на одной картинке, которые идут со смещением по диагонали. Таким образом мы сразу достигнем желаемого эффекта и не нужно будет делать две символизации для полигонов, как в залежах. Земляной обрыв можно сделать разными способами, один из них — сделать перпендикулярные засечки (те же засечки, что мы делали для железных дорог в символизации линий), но чтобы они шли не на исходной линии, а на смещённой внутрь. За счёт смещения засечки не будут пересекать исходную линию, а будут находиться слева от неё и смотреть внутрь полигона. Естественно, исходную линию тоже нужно прорисовать, для этого предназначен первый
LineSymbolizer
, который просто создаёт контур со всеми настройками по умолчанию.
Ну и последний пример, который хотелось бы рассмотреть — хвойный лес:

Итак, что мы здесь видим? Первая символизация — зелёная заливка. Далее идёт заливка кружочками — причём, со смещением, для чего нужно ещё две символизации по аналогии с залежами. И последняя символизация — ель в центре полигона. Ель одна на полигон — значит, это символизация точки, а не заливка полигона, где ели повторялись бы. Значит, нам нужно получить точку центра полигона через геометрическую функцию и в ней сделать символизацию точки:
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">#3E8311</CssParameter>
</Fill>
</PolygonSymbolizer>
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Stroke>
<CssParameter name="stroke-width">0.4</CssParameter>
</Stroke>
</Mark>
<Size>8</Size>
</Graphic>
</GraphicFill>
</Fill>
<Stroke />
<VendorOption name="graphic-margin">0 16 16 0</VendorOption>
</PolygonSymbolizer>
<PolygonSymbolizer>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Stroke>
<CssParameter name="stroke-width">0.4</CssParameter>
</Stroke>
</Mark>
<Size>8</Size>
</Graphic>
</GraphicFill>
</Fill>
<Stroke />
<VendorOption name="graphic-margin">12 4 4 12</VendorOption>
</PolygonSymbolizer>
<PointSymbolizer>
<Geometry>
<ogc:Function name="centroid">
<ogc:PropertyName>the_geom</ogc:PropertyName>
</ogc:Function>
</Geometry>
<Graphic>
<Mark>
<WellKnownName>ttf://Ume P Mincho S3#0x219E</WellKnownName>
<Fill>
<CssParameter name="fill">#000000</CssParameter>
</Fill>
</Mark>
<Size>20</Size>
<Rotation>90</Rotation>
</Graphic>
</PointSymbolizer>
В PointSymbolizer
есть тэг Geometry
— он как раз и указывает, каким образом получить точку из полигона. Мы использовали функцию centroid
— центр полигона. После того, как точка определена, можно ставить в неё ель. Хотя, признаться, это не ель — это двойная стрелка влево из шрифта Ume P Mincho S3
, которая повернута на 90 градусов по часовой стрелке. Рисовать ель мне было лень, а WKT получился бы слишком сложным. Поэтому будем считать это елью за неимением лучшего.
И это всё, что я хотел рассмотреть в трилогии о SLD, хотя возможности SLD на этом не ограничиваются — есть ещё масса неосвещённых возможностей. Есть ещё подписи, различная символизация на разным масштабах, символизация в зависимости от атрибута объекта и многое другое. Но всё сразу охватить крайне сложно, возможно вернусь к стилям спустя некоторое время. А пока задавайте вопросы по изложенному материалу. Как минимум, постараюсь помочь в комментариях. А если я что-то упустил — дополню статью новым примером.