Содержание
Символизация полигонов
2016-09-18 Колосов Сергей
Заключительная часть по 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 на этом не ограничиваются — есть ещё масса неосвещённых возможностей. Есть ещё подписи, различная символизация на разным масштабах, символизация в зависимости от атрибута объекта и многое другое. Но всё сразу охватить крайне сложно, возможно вернусь к стилям спустя некоторое время.