目录
前言
一、最开始的初始世界
1、默认的样式
二、注册事件让地图实现交互
1、绑定事件及注册处理逻辑
三、地图美化,让地图生动起来
1、POM.xml中引入相应的依赖
2、GeoTools加载不同版本的SLD问题
3、加载1.1.0版本的SLD
四、总结
前言
俗话说,人靠衣装马靠鞍。在GIS的世界中同样如此,一幅精美的地图,除了时空地理数据本身的内容和质量外,使用美化样式就是如虎添翼,给地图增加了吸引力。制作精良的地图,绝对离不开它的化妆师,即StyledLayerDescriptor(以下简称SLD)。SLD是OGC关于地图样式的规范,牵涉到OGC的制图标准,其实可以有很多内容可以讲。如果熟悉Web开发的同学,一定对CSS或者LESS很熟悉,你可以把SLD当成地图界的CSS。在Web2.0的界面中,我们的界面做得如此精美,其实就是CSS设计得好,包括合理的配色,文字的大小,布局的协调性等等。
本文结合之前做的一个JavaFX案例讲解,首先分析了原来可视化效果的不足,包括地图没有进行美化、不支持鼠标的缩放和拖动、不支持文本标注的展示等等。本文将重点讲解如何在JavaFx中进行地图美化的开发,同时对JavaFX进行扩展,支持地图的缩放等操作。在讲解SLD时,不仅讲解如何创建模式样式,同时讲解在Geotools中,如何加载SLD1.0.0的版本和SLD1.1.0的版本,通过实例代码的形式给大家展示如何如正确的解析并展示矢量数据。如果您也在学习SLD的相关知识,不妨来这篇博客指导一二。通过本文,相信您可以更加了解GeoTools,对SLD的常规用法掌握更加透彻。
一、最开始的初始世界
还记得最开始的初始世界吗?全球的空间数据展示出来,地图是黑白的。没有明显的填充颜色。并不像上面的地图表现的那么亮眼(大家也可以看到填充了颜色的全球世界还是非常美丽的)。用一张照片给大家看下最开始的样子。
除了它的样式不太好看,其实它还有很多缺点,比如地图不能拖动,不能使用鼠标的滚轮进行放大和缩小。不能按照各个大洲来展示国家信息(比如使用不同的颜色来进行标记),并且在地图上还能进行国家名称的标注,在小层级的时候可以不用展示,只有地图放大到指定层级才会展示。
1、默认的样式
不知道大家还有没有印象,在前面的地图界面展示中,我们使用的都是自带的最简单的样式。可以看看下面的代码:
private void initMap() {
try {
FileDataStore store = FileDataStoreFinder
.getDataStore(this.getClass().getClassLoader().getResource("maps/countries.shp"));
SimpleFeatureSource featureSource = store.getFeatureSource();
map = new MapContent();
map.setTitle("Quickstart");
Style style = SLD.createSimpleStyle(featureSource.getSchema());
FeatureLayer layer = new FeatureLayer(featureSource, style);
map.addLayer(layer);
map.getViewport().setScreenArea(new Rectangle((int) canvas.getWidth(), (int) canvas.getHeight()));
} catch (IOException e) {
e.printStackTrace();
}
}
在这里,Style style = SLD.createSimpleStyle(featureSource.getSchema());这里的样式是根据空间数据的不同类型自动生成的。下面是这个类的源码(部分):
可以看到,这里其实是通过要素的类型,比如线对象、面对象、点对象等进行样式的生成,而这里的颜色其实也是设置得非常粗狂,直接就是fillColor = Color.BLACK,都是黑色信息。 关于如何调整样式,这个下文再进行深入讲解。
二、注册事件让地图实现交互
在初版的功能展示当中,地图是没有办法进行事件交互的,只有一个静态的界面的展示。这一小节,我们来讲解一下如何进行地图事件的注册,让地图和鼠标的交互绑定起来,实现缩放和拖拽的功能。
1、绑定事件及注册处理逻辑
不管是桌面应用还是Web的应用,如何支持外设设备的接入和响应他们的指令。通常都是与一个事件模型分不开的,很多动作的触发都是使用事件机制来驱动。在JavaFx当中进行地图控制和缩放控制也是采用事件模式来实现的。在进行地图绘制的时候,注册以下的时间,关键代码如下所示:
/*
*initial for mouse event
*/
private void initEvent() {
/*
* setting the original coordinate
*/
canvas.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
baseDrageX = e.getSceneX();
baseDrageY = e.getSceneY();
e.consume();
}
});
/*
* translate according to the mouse drag
*/
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent e) {
double difX = e.getSceneX() - baseDrageX;
double difY = e.getSceneY() - baseDrageY;
baseDrageX = e.getSceneX();
baseDrageY = e.getSceneY();
DirectPosition2D newPos = new DirectPosition2D(difX, difY);
DirectPosition2D result = new DirectPosition2D();
map.getViewport().getScreenToWorld().transform(newPos, result);
ReferencedEnvelope env = new ReferencedEnvelope(map.getViewport().getBounds());
env.translate(env.getMinimum(0) - result.x, env.getMaximum(1) - result.y);
doSetDisplayArea(env);
e.consume();
}
});
/*
* double clicks to restore to original map
*/
canvas.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent t) {
if (t.getClickCount() > 1) {
doSetDisplayArea(map.getMaxBounds());
}
t.consume();
}
});
/*
* scroll for zoom in and out
*/
canvas.addEventHandler(ScrollEvent.SCROLL, new EventHandler<ScrollEvent>() {
@Override
public void handle(ScrollEvent e) {
ReferencedEnvelope envelope = map.getViewport().getBounds();
double percent = e.getDeltaY() / canvas.getWidth();
double width = envelope.getWidth();
double height = envelope.getHeight();
double deltaW = width * percent;
double deltaH = height * percent;
envelope.expandBy(deltaW, deltaH);
doSetDisplayArea(envelope);
e.consume();
}
});
}
在加上以上的功能后,地图可以实现缩放和拖动了。基本达到我们控制的目标。
三、地图美化,让地图生动起来
这里说的美化底图,其实也是比较有限的美化哈。个人的配色和搭配也是一般般。这里用生动一词想表达的是通过SLD可以让地图美观,希望大家在使用的时候发挥主观能动性,能实现真正的美化地图的外观。本节将主要讲解如何自己实现加载SLD1.0.0和SLD1.1.0两个版本的样式。很多博客没有讲清楚如何正确的加载样式,这里给各位朋友讲清楚。
1、POM.xml中引入相应的依赖
在我们的案例中,我们使用的GeoTools的相关组件,与一般的展示不一样,这里我们要引入一些处理样式的组件。在下面的Pom.xml中定义好依赖的资源包。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ldj.tutorial</groupId>
<artifactId>geotools-fx</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<geotools.version>28.2</geotools.version>
<fxgraphics2d.version>1.3</fxgraphics2d.version>
</properties>
<repositories>
<!--从22.x后geotools由OSGeo管理-->
<repository>
<id>osgeo</id>
<name>OSGeo Release Repository</name>
<url>https://repo.osgeo.org/repository/release/</url>
<snapshots><enabled>false</enabled></snapshots>
<releases><enabled>true</enabled></releases>
</repository>
<repository>
<id>osgeo-snapshot</id>
<name>OSGeo Snapshot Repository</name>
<url>https://repo.osgeo.org/repository/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
<repository>
<id>GeoSolutions</id>
<url>http://maven.geo-solutions.it/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-swing</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>${geotools.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.geotools.xsd/gt-xsd-sld -->
<dependency>
<groupId>org.geotools.xsd</groupId>
<artifactId>gt-xsd-sld</artifactId>
<version>${geotools.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.geotools.xsd/gt-xsd-core -->
<dependency>
<groupId>org.geotools.xsd</groupId>
<artifactId>gt-xsd-core</artifactId>
<version>${geotools.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.geotools/gt-xml -->
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-xml</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.jfree</groupId>
<artifactId>fxgraphics2d</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</project>
2、GeoTools加载不同版本的SLD问题
关于SLD的原文说明可以看如下的介绍,SLD - Introduction,
Geospatial data (vector and raster) have no intrinsic visual component. In order to see data, it must be styled. Styling specifies color, thickness, and other visible attributes used to render data on a map. A WMS provides a set of style options for each data set; however these are preconfigured by the server, and the user cannot create, inspect, modify a style. The Styled Layer Descriptor (SLD) is a standard that enables an application to configure in an XML document how to properly portray layers and legends in a WMS. It uses Symbology Ending (SE) to specify styling of features and coverages. The SLD Profile of WMS enhances a WMS with additional operations to support styling of features from WFS and coverages from WCS.
History
Version number | Release date | Summary of Changes |
---|---|---|
1.1 | 2007-06-29 | “1.0 specification was split into SE and SLD, more functionality was added” |
1.0 | 2002-09-19 |
可以看到SLD也有两个版本,2002年9月19发布第一个版本。在时隔五年之后发布了1.1.0版本。这两个差别的xml语法定法差别很大,同时比如在GeoServer中一些版本对1.1.0支持得不够友好。下面给出一个1.0.0的SLD样式定义文件:
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamedLayer>
<Name>countries</Name>
<UserStyle>
<Title>Simple Polygon</Title>
<FeatureTypeStyle>
<Rule>
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">#21e91e</CssParameter>
</Fill>
<Stroke>
<!-- <CssParameter name="stroke">#0000FF</CssParameter> -->
<CssParameter name="stroke">#3f51b5</CssParameter>
<CssParameter name="stroke-width">1</CssParameter>
</Stroke>
</PolygonSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
在这里我们设置了边框颜色和填充颜色,让全球世界范围看起来好看了一丢丢,主功能如下:
在设置了颜色之后,世界变得艳丽了许多,也明亮了许多。
3、加载1.1.0版本的SLD
上面的1.0版本的SLD是通过官方提供的样式进行生成的,比较简单。大家知道,如果大家使用的是QGIS的话,一定会用到里面的样式和标绘功能,使用QGIS进行地图标绘完成之后,可以将标绘的样式导出为SLD,可以部分直接放在GeoServer中使用。也可以放到JavaFx程序中进行使用。我使用的QGIS3.X的版本,其生成的SLD都是1.1版本的。因此我们需要实现兼容两种不同版本的SLD。
public Style createStyleFromSld(String uri)
throws XPathExpressionException, IOException, SAXException, ParserConfigurationException {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document xmlDocument = db.parse(uri);
XPath xPath = XPathFactory.newInstance().newXPath();
String version = xPath.compile("/StyledLayerDescriptor/@version").evaluate(xmlDocument);
Configuration sldConf;
if (version != null && version.startsWith("1.1")) {
sldConf = new org.geotools.sld.v1_1.SLDConfiguration();
} else {
sldConf = new org.geotools.sld.SLDConfiguration();
}
StyledLayerDescriptor sld = (StyledLayerDescriptor) new org.geotools.xsd.DOMParser(sldConf, xmlDocument)
.parse();
NamedLayer l = (NamedLayer) sld.getStyledLayers()[0];
Style style = l.getStyles()[0];
return style;
}
然后我们使用QGIS进行标绘后,将样式导出为SLD,在JavaFx中加载这个导出的SLD样式文件。为了实现页面的刷新,我们需要定义个后台线程,有变化后通过线程来更新JavaFx组件。线程的核心代码如下:
private static final double PAINT_HZ = 50.0;
private void initPaintThread() {
ScheduledService<Boolean> svc = new ScheduledService<Boolean>() {
protected Task<Boolean> createTask() {
return new Task<Boolean>() {
protected Boolean call() {
Platform.runLater(() -> {
drawMap(gc);
});
return true;
}
};
}
};
svc.setPeriod(Duration.millis(1000.0 / PAINT_HZ));
svc.start();
}
在1.1.0版本的SLD中,我们将按照不同的大洲进行分类,比如亚洲和非洲的颜色就不一样,同时定义了国家标注的大小层级。这里直接给出实例文件,关于SLD的样式文件内容,以后可以专门进行讲解。
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd" xmlns:se="http://www.opengis.net/se" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogc="http://www.opengis.net/ogc">
<NamedLayer>
<se:Name>countries</se:Name>
<UserStyle>
<se:Name>countries</se:Name>
<se:FeatureTypeStyle>
<se:Rule>
<se:Name>Africa</se:Name>
<se:Description>
<se:Title>Africa</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>Africa</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#df77c5</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name>Antartica</se:Name>
<se:Description>
<se:Title>Antartica</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>Antartica</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#91cb5a</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name>Asia</se:Name>
<se:Description>
<se:Title>Asia</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>Asia</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#cf413e</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name>Europe</se:Name>
<se:Description>
<se:Title>Europe</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>Europe</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#6aedef</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name>North America</se:Name>
<se:Description>
<se:Title>North America</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>North America</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#4c71e8</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name>Oceania</se:Name>
<se:Description>
<se:Title>Oceania</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>Oceania</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#dec05e</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name>South America</se:Name>
<se:Description>
<se:Title>South America</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal>South America</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#39c960</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:Name></se:Name>
<se:Description>
<se:Title>CONTINENT is ''</se:Title>
</se:Description>
<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
<ogc:Or>
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
<ogc:Literal></ogc:Literal>
</ogc:PropertyIsEqualTo>
<ogc:PropertyIsNull>
<ogc:PropertyName>CONTINENT</ogc:PropertyName>
</ogc:PropertyIsNull>
</ogc:Or>
</ogc:Filter>
<se:PolygonSymbolizer>
<se:Fill>
<se:SvgParameter name="fill">#7a2aca</se:SvgParameter>
</se:Fill>
<se:Stroke>
<se:SvgParameter name="stroke">#232323</se:SvgParameter>
<se:SvgParameter name="stroke-width">1</se:SvgParameter>
<se:SvgParameter name="stroke-linejoin">bevel</se:SvgParameter>
</se:Stroke>
</se:PolygonSymbolizer>
</se:Rule>
<se:Rule>
<se:MinScaleDenominator>25000</se:MinScaleDenominator>
<se:MaxScaleDenominator>124672208</se:MaxScaleDenominator>
<se:TextSymbolizer>
<se:Label>
<ogc:PropertyName>NAME</ogc:PropertyName>
</se:Label>
<se:Font>
<se:SvgParameter name="font-family">SimSun</se:SvgParameter>
<se:SvgParameter name="font-size">13</se:SvgParameter>
</se:Font>
<se:LabelPlacement>
<se:PointPlacement>
<se:AnchorPoint>
<se:AnchorPointX>0</se:AnchorPointX>
<se:AnchorPointY>0.5</se:AnchorPointY>
</se:AnchorPoint>
</se:PointPlacement>
</se:LabelPlacement>
<se:Fill>
<se:SvgParameter name="fill">#000000</se:SvgParameter>
</se:Fill>
<se:VendorOption name="maxDisplacement">1</se:VendorOption>
</se:TextSymbolizer>
</se:Rule>
</se:FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
经过以上的定义,基本实现我们刚开始的业务需求。实现的功能界面效果如下:
四、总结
以上就是本文的主要内容,本文结合之前做的一个JavaFX案例讲解,首先分析了原来可视化效果的不足,包括地图没有进行美化、不支持鼠标的缩放和拖动、不支持文本标注的展示等等。本文将重点讲解如何在JavaFx中进行地图美化的开发,同时对JavaFX进行扩展,支持地图的缩放等操作。在讲解SLD时,不仅讲解如何创建模式样式,同时讲解在Geotools中,如何加载SLD1.0.0的版本和SLD1.1.0的版本,通过实例代码的形式给大家展示如何如正确的解析并展示矢量数据。行文仓促,定有不足之处,欢迎各位专家朋友在评论区留下真知灼见,不胜感激。