Это вторая часть по 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 line style</Title>
<FeatureTypeStyle>
<Rule>
<Title>Здесь идёт заголовок легенды</Title>
<!-- ТЕЛО СТИЛЯ ИДЁТ ЗДЕСЬ -->
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
LineSymbolizer
Начнём мы снова со стиля по умолчанию, который GeoServer использует для линий.

Стиль по сравнению с точками весьма простой — в нём задаётся только цвет линии:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke">#0000FF</CssParameter>
</Stroke>
</LineSymbolizer>
Внутри LineSymbolizer
допустимо использование следующих тэгов:
Geometry
— опциональный, определяет источник геометрииStroke
— обязательный, непосредственная стилизация линииPerpendicularOffset
— опциональный, создаёт линию, которая параллельна исходной и отстоит от неё на заданное количество пикселей
Geometry
через определённые трансформации изменяет исходную геометрию и применяет стилизацию к той геометрии, которая получилась на выходе. Мы затронем эту функцию ближе к концу статьи. Стоит заметить, что символизация линий может применяться и к точкам, и к полигонам. Точка будет расцениваться как линия нулевой длины с горизонтальной ориентацией, а в случае с полигонами каждый его контур будет расцениваться как отдельная линия.
PerpendicularOffset
, как правило, используется для создания копии исходной линии параллельно основной линии. Используется не так часто, однако в некоторых случаях наиболее удобное решение.
Stroke
единственный из тэгов внутри LineSymbolizer
, который является обязательным. Вероятней всего, он покроет все ваши запросы. Он заслуживает детального рассмотрения.
Stroke
Мы рассмотрим два тэга внутри Stroke
— GraphicStroke
и CssParameter
GraphicStroke
может содержать всё то, что мы рассматривали в символизации точек.
Тэгов CssParameter
может быть много. Они задают цвет, толщину и другие базовые параметры символизации линии в зависимости от значения атрибута name
:
stroke
— цвет заливки в формате#RRGGBB
(по умолчанию#000000
)stroke-width
— толщина в пикселях. Значение по умолчанию — 1stroke-opacity
— непрозрачность, значение от 0 (полностью прозрачный) до 1 (полностью непрозрачный). Значение по умолчанию — 1stroke-linejoin
— определяет, как линии будут отображаться в месте пересечения сегментов. Возможные значения —mitre
(острые углы, значение по умолчанию),round
(скруглённые углы) иbevel
(диагональные углы)stroke-linecap
— как линии будут отображаться на концах. Допустимыbutt
(резкие квадратные грани, значение по умолчанию),round
(округлые грани) и square (слегка вытянутые грани)stroke-dasharray
— Всевозможные варианты штрихпунктирных линий. Значением является серия чисел, разделённых пробелами. Нечётные числа (первое, третье, …) задаёт длину штриха в пикселях. Чётные числа (второе, четвёртое, …) задают длину разрыва между штрихами. Например, последовательность 4 2 2 2 означает, что сначала идёт 4 пикселя линии, затем 2 пикселя пустоты, затем 2 пикселя линии и ещё 2 пикселя пустоты. После этого узор повторитсяstroke-dashoffset
— смещение узораstroke-dasharray
относительно начала линии в пикселях. По умолчанию 0
Примеры
Начнём с несложного примера, где зададим цвет, толщину и небольшую прозрачность:

Этого можно добиться с помощью следующего стиля:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke">#e55e30</CssParameter>
<CssParameter name="stroke-width">0.2</CssParameter>
<CssParameter name="stroke-opacity">0.8</CssParameter>
</Stroke>
</LineSymbolizer>
За счёт прозрачности и малой толщины линия получилась «лёгкой» — такая не будет брать на себя акцент в сложном слое, состоящим из множества объектов, но в то же время её хорошо видно. Теперь сделаем штрихпунктирную линию:

Соответствующий стиль:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke">#86523a</CssParameter>
<CssParameter name="stroke-width">2</CssParameter>
<CssParameter name="stroke-dasharray">16 2 2 2 2 2</CssParameter>
</Stroke>
</LineSymbolizer>
Сначала идёт штрих 16 пикселей, затем 2 пикселя пробел, 2 пикселя линия, 2 пробел, 2 линия и в конце ещё 2 пикселя пробел, чтобы при повторении узора 16 пикселей не слились с последним штрихом в 2 пикселя. stroke-dasharray
работает и с GraphicStroke
, что позволяет задавать, сколько места отвести под фигуру, а сколько сделать отступ:

… и стиль, с помощью которого сделана эта символизация:
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>star</WellKnownName>
<Fill>
<CssParameter name="fill">#fcff05</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">#000000</CssParameter>
<CssParameter name="stroke-width">0.3</CssParameter>
</Stroke>
</Mark>
<Size>10</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">10 10</CssParameter>
</Stroke>
</LineSymbolizer>
Как я уже говорил, GraphicStroke
может содержать все те тэги, которые были рассмотрены в символизации точек: стандартные и расширенные символы, картинки, символы шрифта и WKT. В примере выше мы использовали стандартный символ — звезду, для которой задали границу и заливку. Проблема подобной символизации линий в том, что контур линии разглядеть довольно сложно, особенно в местах скопления нескольких линий. Было бы неплохо, чтобы все звёзды были «сцеплены» друг с другом. И это вполне реально сделать! Для этого нужно задать два элемента LineSymbolizer
— один проведёт контур линии, второй — звёзды:

Теперь линии прослеживаются гораздо лучше. Такой эффект достигается за счёт следующего стиля:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke">#000000</CssParameter>
<CssParameter name="stroke-width">0.5</CssParameter>
</Stroke>
</LineSymbolizer>
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>star</WellKnownName>
<Fill>
<CssParameter name="fill">#fcff05</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">#000000</CssParameter>
<CssParameter name="stroke-width">0.3</CssParameter>
</Stroke>
</Mark>
<Size>10</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">10 10</CssParameter>
</Stroke>
</LineSymbolizer>
Порядок следования тэгов важен! Здесь мы сначала определили линию, поэтому она идёт ниже звёзд. Если поменять эти LineSymbolizer
местами, чёрная линия получится сверху и мы получим перечёркнутые звёзды.
Примеры из топографии
Обозначение звёздами выглядит, конечно, здорово, но вряд ли будет применяться в топокартах, поэтому рассмотрим несколько реальных применений. Например, строящиеся узкоколейные железные дороги:

Для воспроизведения данного стиля сделаем две символизации — штрихпунктирная линия и символ shape://vertline
для перпендикулярных засечек:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke-width">1.5</CssParameter>
<CssParameter name="stroke-dasharray">14 4</CssParameter>
</Stroke>
</LineSymbolizer>
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>shape://vertline</WellKnownName>
<Stroke />
</Mark>
<Size>6</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">4 14</CssParameter>
<CssParameter name="stroke-dashoffset">14</CssParameter>
</Stroke>
</LineSymbolizer>
Первое, на что стоит обратить внимание, — мы задали смещение перпендикулярных засечек через stroke-dashoffset
, чтобы засечки появились именно на середине штриха линии (значение вычислено методом проб и ошибок). Второе — пустой тэг <Stroke />
. Есть большая разница между пустым тегом и отсутствующим вовсе. Если тэга Stroke
не будет — не будет и границы. Если будет пустой тэг — все значения возьмутся по умолчанию (чёрная линия толщиной в пиксель).
Идём дальше — границы полярных владений:

Здесь можно обойтись тремя тэгами LineSymbolizer
:
- Толстая штрихпунктирная линия с небольшой прозрачностью, которая идёт по основному контуру и имеет равную длину штриха и разрыва
- Перпендикулярные засечки, расстояние между которыми рассчитано так, чтобы попадать на начало и конец штриха
- Кружочки без заливки с границей, которые идут в разрывах линии
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke-width">4</CssParameter>
<CssParameter name="stroke-dasharray">8 8</CssParameter>
<CssParameter name="stroke-opacity">0.6</CssParameter>
</Stroke>
</LineSymbolizer>
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>shape://vertline</WellKnownName>
<Stroke />
</Mark>
<Size>10</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">8 0</CssParameter>
<CssParameter name="stroke-dashoffset">5</CssParameter>
</Stroke>
</LineSymbolizer>
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Stroke />
</Mark>
<Size>4</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">4 12</CssParameter>
<CssParameter name="stroke-dashoffset">6</CssParameter>
</Stroke>
</LineSymbolizer>
Более сложные примеры
Здесь мы разберём примеры, когда линия — не совсем линия. Например, древние исторические стены:

Здесь отлично подойдёт WKT. Нам нужно лишь задать минимальную часть повторяющегося сегмента, остальное GeoServer сделает за нас:
<LineSymbolizer>
<Stroke>
<GraphicStroke>
<Graphic>
<Mark>
<WellKnownName>
wkt://MULTILINESTRING(
(0 1, 0 -1),
(0 -1, 2 -1),
(2 -1, 2 1),
(2 1, 4 1)
)
</WellKnownName>
<Stroke />
</Mark>
<Size>6</Size>
</Graphic>
</GraphicStroke>
<CssParameter name="stroke-dasharray">6 6</CssParameter>
</Stroke>
</LineSymbolizer>
В самом узоре нет ничего сложного. Он состоит из четырех линий, которые образуют непрерывную кривую. На картинке слева пунктиром показана исходная линия, синим — символизация WKT. GeoServer будет повторять этот узор, в результате чего точка (0, 1) замкнется с (4.1) и получится наша непрерывная каменная стена.
stroke-dasharray
понадобился, так как GeoServer слишком рано начинал повторять линию, что приводило к тому, что следующий узор налезал на предыдущий.
Ещё один интересный пример — зимние дороги:

Здесь идёт две линии со смещением, для чего нам понадобится тэг PerpendicularOffset
. Одну линию мы разместим на 4 пикселя справа от исходной, вторую на 4 пикселя слева, обоим линиям зададим одинаковый пунктир:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke-width">2</CssParameter>
<CssParameter name="stroke-dasharray">2 4</CssParameter>
</Stroke>
<PerpendicularOffset>-4</PerpendicularOffset>
</LineSymbolizer>
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke-width">2</CssParameter>
<CssParameter name="stroke-dasharray">2 4</CssParameter>
</Stroke>
<PerpendicularOffset>4</PerpendicularOffset>
</LineSymbolizer>
Ну и последнее, что хотелось бы затронуть — предназначение тэга Geometry
. С помощью этого тэга можно выделить отдельные точки линии и сделать символизацию для этих точек через PointSymbolizer
. Как это работает — покажу на небольшом примере. Допустим, нам нужно, чтобы в конце линии была стрелка:

Для этого нам потребуется символизация линии, чтобы было видно саму линию. Далее нам необходимо получить конечную точку линии и поместить туда стрелку через символизацию точки:
<LineSymbolizer>
<Stroke>
<CssParameter name="stroke">#0000FF</CssParameter>
<CssParameter name="stroke-width">2</CssParameter>
</Stroke>
</LineSymbolizer>
<PointSymbolizer>
<Geometry>
<ogc:Function name="endPoint">
<ogc:PropertyName>the_geom</ogc:PropertyName>
</ogc:Function>
</Geometry>
<Graphic>
<Mark>
<WellKnownName>shape://oarrow</WellKnownName>
<Fill>
<CssParameter name="fill">#0000FF</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">#0000FF</CssParameter>
<CssParameter name="stroke-width">2</CssParameter>
</Stroke>
</Mark>
<Size>30</Size>
<Rotation>
<ogc:Function name="endAngle">
<ogc:PropertyName>the_geom</ogc:PropertyName>
</ogc:Function>
</Rotation>
</Graphic>
</PointSymbolizer>
LineSymbolizer
пропустим, его уже достаточно рассмотрели. В символизации точки мы определяем, что мы возьмём последнюю точку (функция endPoint
) из геометрии линии (the_geom
). Затем в Mark
мы задаём стрелку и её заливку с границей. Но если стрелку указать таким образом, то все они будут смотреть в одну сторону, что нас не устраивает. Для того, чтобы угол стрелки совпадал с углом последнего сегмента, зададим для символизации точки поворот Rotation
равным углу последнего сегмента endAngle
. Всего функций очень много и порой они очень помогают.
Пожалуй, это все примеры, которые я хотел рассмотреть. Если статья не отвечает на ваши вопросы, если что-то осталось непонятным или же вы по-прежнему не знаете, как настроить символизацию для какого-то случая — напишите об этом в комментариях.
Добрый день! у вас в стиле со смещением относительно начала линии видно, что конец линии зачастую выглядит по-другому, чем начало. Есть ли способы, чтобы вне зависимости от длины линии, начало штриха всегда начиналось в начале линии, а конец последнего штриха заканчивался точно в конце символизируемой линии?
К сожалению, не знаю прямых способов решения такой проблемы. Да и сложно представить решение, если линия кончается на середине паттерна. Можно добавить в начало и конец линии какую-нибудь фигуру (кружок, квадрат или что-то ещё), тогда линия будет смотреться более целостной и будет хорошо видно её начало и конец.