geotools学习(七)地图样式
样式
请确保您的pom.xml包含以下内容:
<dependencies>
<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-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
<repository>
<id>osgeo</id>
<name>Open Source Geospatial Foundation Repository</name>
<url>http://download.osgeo.org/webdav/geotools/</url>
</repository>
<repository>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>boundless</id>
<name>Boundless Maven Repository</name>
<url>http://repo.boundlessgeo.com/main</url>
</repository>
</repositories>
示例
下面示例代码是可用的:
- 直接来自git: stylela .java
- 在下载GeoTools源代码时包括在演示目录中
主程序
1 创建org.geotools.tutorial.style包和 StyleLab类
2 复制并粘贴以下代码:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
*
* This file is hereby placed into the Public Domain. This means anyone is
* free to do whatever they wish with this file. Use it well and enjoy!
*/
package org.geotools.tutorial.style;
import java.awt.Color;
import java.io.File;
import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.swing.JMapFrame;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.swing.dialog.JExceptionReporter;
import org.geotools.swing.styling.JSimpleStyleDialog;
import org.geotools.xml.styling.SLDParser;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.FilterFactory;
public class StyleLab {
static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory();
static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory();
public static void main(String[] args) throws Exception {
StyleLab me = new StyleLab();
me.displayShapefile();
}
显示一个shapefile
如果你已经通过以前的实验,这种方法的大部分你会看起来很熟悉:
/**
* Prompts the user for a shapefile (unless a filename is provided on the command line; then
* creates a simple Style and displays the shapefile on screen
*/
private void displayShapefile() throws Exception {
File file = JFileDataStoreChooser.showOpenFile("shp", null);
if (file == null) {
return;
}
FileDataStore store = FileDataStoreFinder.getDataStore(file);
FeatureSource featureSource = store.getFeatureSource();
// Create a map content and add our shapefile to it
MapContent map = new MapContent();
map.setTitle("StyleLab");
// Create a basic Style to render the features
Style style = createStyle(file, featureSource);
// Add the features and the associated Style object to
// the MapContent as a new Layer
Layer layer = new FeatureLayer(featureSource, style);
map.addLayer(layer);
// Now display the map
JMapFrame.showMap(map);
}
需要注意的主要事情是,我们正在调用一个createStyle方法来设置映射层的样式。接下来让我们看看这个方法。
创建一个样式
此方法首先查看是否有与shapefile关联的SLD文档(样式化的层描述符)。如果找到一个,它将处理该文件以创建样式。否则,它会显示一个JSimpleStyleDialog来提示用户选择样式:
/**
* Create a Style to display the features. If an SLD file is in the same directory as the
* shapefile then we will create the Style by processing this. Otherwise we display a
* JSimpleStyleDialog to prompt the user for preferences.
*/
private Style createStyle(File file, FeatureSource featureSource) {
File sld = toSLDFile(file);
if (sld != null) {
return createFromSLD(sld);
}
SimpleFeatureType schema = (SimpleFeatureType) featureSource.getSchema();
return JSimpleStyleDialog.showDialog(null, schema);
}
以下两种方法根据shapefile名称计算出SLD文件名,如果找到SLD文档,则进行处理
/** Figure out if a valid SLD file is available. */
public File toSLDFile(File file) {
String path = file.getAbsolutePath();
String base = path.substring(0, path.length() - 4);
String newPath = base + ".sld";
File sld = new File(newPath);
if (sld.exists()) {
return sld;
}
newPath = base + ".SLD";
sld = new File(newPath);
if (sld.exists()) {
return sld;
}
return null;
}
/** Create a Style object from a definition in a SLD document */
private Style createFromSLD(File sld) {
try {
SLDParser stylereader = new SLDParser(styleFactory, sld.toURI().toURL());
Style[] style = stylereader.readXML();
return style[0];
} catch (Exception e) {
JExceptionReporter.showDialog(e, "Problem creating style");
}
return null;
}
手工创建样式
到目前为止,我们看到的方法就是我们在这个简单应用程序中真正需要的所有方法。但是现在让我们看看如何通过编程创建样式。这说明了前面代码中幕后发生的一些事情。它还向您介绍了StyleFactory和FilterFactory,它们在您可以创建的样式中提供了大量的灵活性
在下面的代码中,第一个方法计算出我们的shapefile中的几何类型:点、线或多边形。然后调用特定于几何的方法来创建样式对象。
/**
* Here is a programmatic alternative to using JSimpleStyleDialog to get a Style. This methods
* works out what sort of feature geometry we have in the shapefile and then delegates to an
* appropriate style creating method.
*/
private Style createStyle2(FeatureSource featureSource) {
SimpleFeatureType schema = (SimpleFeatureType) featureSource.getSchema();
Class geomType = schema.getGeometryDescriptor().getType().getBinding();
if (Polygon.class.isAssignableFrom(geomType)
|| MultiPolygon.class.isAssignableFrom(geomType)) {
return createPolygonStyle();
} else if (LineString.class.isAssignableFrom(geomType)
|| MultiLineString.class.isAssignableFrom(geomType)) {
return createLineStyle();
} else {
return createPointStyle();
}
}
注意:
- 每个特定于几何图形的方法都在创建一种Symbolizer类型:控制如何呈现特性的类
- 每个方法都将Symbolizer包装在一个Rule中,然后是一个FeatureTypeStyle,最后是一个Style
- 在现实生活中,在一个FeatureTypeStyle中有多个规则是很常见的。例如,我们可以创建一个规则来在地图缩小时绘制功能,另一个规则用于显示详细信息
Selection
本教程综合了我们在前面的例子中介绍的许多技术和类。如果你至少已经学习过快速入门、CSV2SHP和风格教程,那就再好不过了。
我们将要:
- 创建自定义地图光标工具,以在用户单击地图时选择功能
- 向JMapFrame添加工具栏按钮以启动此工具
- 使用过滤器来查找鼠标点击下面或附近的特性
- 创建渲染样式来绘制不同颜色的选定和未选定的特性
Dependencies
<properties>
<geotools.version>14.0</geotools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-shapefile</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-swing</artifactId>
<version>${geotools.version}</version>
</dependency>
</dependencies>
示例代码是可用的:
- 直接从源存储库clone: SelectionLab.java
- 在下载GeoTools源代码时包括在演示目录中
主程序
1.请创建文件SelectionLab.java
2.复制并粘贴以下代码:
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-2008, Open Source Geospatial Foundation (OSGeo)
*
* This file is hereby placed into the Public Domain. This means anyone is
* free to do whatever they wish with this file. Use it well and enjoy!
*/
package org.geotools.tutorial.style;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JToolBar;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.FeatureLayer;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.Mark;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;
import org.geotools.swing.JMapFrame;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.swing.event.MapMouseEvent;
import org.geotools.swing.tool.CursorTool;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.identity.FeatureId;
/**
* In this example we create a map tool to select a feature clicked with the mouse. The selected
* feature will be painted yellow.
*/
public class SelectionLab {
/*
* Factories that we will use to create style and filter objects
*/
private StyleFactory sf = CommonFactoryFinder.getStyleFactory();
private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
/*
* Convenient constants for the type of feature geometry in the shapefile
*/
private enum GeomType {
POINT,
LINE,
POLYGON
};
/*
* Some default style variables
*/
private static final Color LINE_COLOUR = Color.BLUE;
private static final Color FILL_COLOUR = Color.CYAN;
private static final Color SELECTED_COLOUR = Color.YELLOW;
private static final float OPACITY = 1.0f;
private static final float LINE_WIDTH = 1.0f;
private static final float POINT_SIZE = 10.0f;
private JMapFrame mapFrame;
private SimpleFeatureSource featureSource;
private String geometryAttributeName;
private GeomType geometryType;
/*
* The application method
*/
public static void main(String[] args) throws Exception {
SelectionLab me = new SelectionLab();
File file = JFileDataStoreChooser.showOpenFile("shp", null);
if (file == null) {
return;
}
me.displayShapefile(file);
}
在样式教程中,您应该很熟悉这些内容。我们已经添加了一些常量和类变量,将在创建样式时使用它们。
一个细微的区别是,我们现在使用的是FilterFactory2,而不是FilterFactory。这个类添加了额外的方法,其中之一是我们在基于鼠标点击选择特性时需要的
Shapefile查看器与自定义地图工具
接下来,我们添加displayShapefile方法,它也非常类似于我们在样式教程中使用的方法。
/**
* This method connects to the shapefile; retrieves information about its features; creates a
* map frame to display the shapefile and adds a custom feature selection tool to the toolbar of
* the map frame.
*/
public void displayShapefile(File file) throws Exception {
FileDataStore store = FileDataStoreFinder.getDataStore(file);
featureSource = store.getFeatureSource();
setGeometry();
/*
* Create the JMapFrame and set it to display the shapefile's features
* with a default line and colour style
*/
MapContent map = new MapContent();
map.setTitle("Feature selection tool example");
Style style = createDefaultStyle();
Layer layer = new FeatureLayer(featureSource, style);
map.addLayer(layer);
mapFrame = new JMapFrame(map);
mapFrame.enableToolBar(true);
mapFrame.enableStatusBar(true);
/*
* Before making the map frame visible we add a new button to its
* toolbar for our custom feature selection tool
*/
JToolBar toolBar = mapFrame.getToolBar();
JButton btn = new JButton("Select");
toolBar.addSeparator();
toolBar.add(btn);
/*
* When the user clicks the button we want to enable
* our custom feature selection tool. Since the only
* mouse action we are intersted in is 'clicked', and
* we are not creating control icons or cursors here,
* we can just create our tool as an anonymous sub-class
* of CursorTool.
*/
btn.addActionListener(
e ->
mapFrame.getMapPane()
.setCursorTool(
new CursorTool() {
@Override
public void onMouseClicked(MapMouseEvent ev) {
selectFeatures(ev);
}
}));
/** Finally, we display the map frame. When it is closed this application will exit. */
mapFrame.setSize(600, 600);
mapFrame.setVisible(true);
}
注意,我们通过向JMapFrame的工具栏添加一个按钮来定制它。当用户单击此按钮时,将为映射窗口设置一个新的光标工具。这个工具只有一个方法来响应地图区域中的鼠标点击
用户点击了什么功能
接下来,我们将添加在用户处于选择模式时调用的方法。我们的自定义工具栏按钮已经按下,用户现在已经点击了地图上的某个地方
该方法首先在鼠标位置周围创建一个5x5像素宽的矩形,以便更容易地选择点和线特性。它从像素坐标转换为世界坐标,并用于创建一个过滤器来识别在鼠标点击下或附近的要素
/**
* This method is called by our feature selection tool when the user has clicked on the map.
*
* @param ev the mouse event being handled
*/
void selectFeatures(MapMouseEvent ev) {
System.out.println("Mouse click at: " + ev.getWorldPos());
/*
* Construct a 5x5 pixel rectangle centred on the mouse click position
*/
Point screenPos = ev.getPoint();
Rectangle screenRect = new Rectangle(screenPos.x - 2, screenPos.y - 2, 5, 5);
/*
* Transform the screen rectangle into bounding box in the coordinate
* reference system of our map context. Note: we are using a naive method
* here but GeoTools also offers other, more accurate methods.
*/
AffineTransform screenToWorld = mapFrame.getMapPane().getScreenToWorldTransform();
Rectangle2D worldRect = screenToWorld.createTransformedShape(screenRect).getBounds2D();
ReferencedEnvelope bbox =
new ReferencedEnvelope(
worldRect, mapFrame.getMapContent().getCoordinateReferenceSystem());
/*
* Create a Filter to select features that intersect with
* the bounding box
*/
Filter filter = ff.intersects(ff.property(geometryAttributeName), ff.literal(bbox));
/*
* Use the filter to identify the selected features
*/
try {
SimpleFeatureCollection selectedFeatures = featureSource.getFeatures(filter);
Set<FeatureId> IDs = new HashSet<>();
try (SimpleFeatureIterator iter = selectedFeatures.features()) {
while (iter.hasNext()) {
SimpleFeature feature = iter.next();
IDs.add(feature.getIdentifier());
System.out.println(" " + feature.getIdentifier());
}
}
if (IDs.isEmpty()) {
System.out.println(" no feature selected");
}
displaySelectedFeatures(IDs);
} catch (Exception ex) {
ex.printStackTrace();
}
}
注意,我们在这个方法中使用的是intersects过滤器,而不是bbox(包围框)过滤器。包围框过滤器非常快,但它只会测试鼠标单击周围的矩形是否在每个特性的信封内,而不是边界内。对于这个应用程序,这不是我们想要做的。要了解原因,请考虑下面这个例子……
image.png
蓝色的形状是一个单一的多多边形的一部分,这是标准几何类型的多边形特征在shapefile。使用包围框过滤器,单击橙色形状将选择它和所有蓝色形状,因为单击区域在它们的信封(灰色矩形)内。
根据所选内容创建样式
一旦上面的方法确定了选择了哪些特性,如果有的话,它会将它们的特性id传递给displaySelected方法。它调用两种样式创建方法中的一种,然后用更新的样式重新显示地图:
/**
* Sets the display to paint selected features yellow and unselected features in the default
* style.
*
* @param IDs identifiers of currently selected features
*/
public void displaySelectedFeatures(Set<FeatureId> IDs) {
Style style;
if (IDs.isEmpty()) {
style = createDefaultStyle();
} else {
style = createSelectedStyle(IDs);
}
Layer layer = mapFrame.getMapContent().layers().get(0);
((FeatureLayer) layer).setStyle(style);
mapFrame.getMapPane().repaint();
}
默认样式
这个方法为所有的特性创建一个单一规则的样式,使用行和填充类顶部定义的常量:
/** Create a default Style for feature display */
private Style createDefaultStyle() {
Rule rule = createRule(LINE_COLOUR, FILL_COLOUR);
FeatureTypeStyle fts = sf.createFeatureTypeStyle();
fts.rules().add(rule);
Style style = sf.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
选定的样式
该方法创建一个样式,其中一个规则用于选中的特性,用高亮颜色表示,另一个规则用于未选中的特性。然后,在Style对象中包装这两个规则
/**
* Create a Style where features with given IDs are painted yellow, while others are painted
* with the default colors.
*/
private Style createSelectedStyle(Set<FeatureId> IDs) {
Rule selectedRule = createRule(SELECTED_COLOUR, SELECTED_COLOUR);
selectedRule.setFilter(ff.id(IDs));
Rule otherRule = createRule(LINE_COLOUR, FILL_COLOUR);
otherRule.setElseFilter(true);
FeatureTypeStyle fts = sf.createFeatureTypeStyle();
fts.rules().add(selectedRule);
fts.rules().add(otherRule);
Style style = sf.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
注意,第一个规则包含一个过滤器,它是用FilterFactory2创建的。id的方法。这意味着该规则将只适用于所选的特性。
第二个规则被标记为使用setElseFilter方法的替代(适用于所有其他特性)。
创建一个规则和符号
好了,我们快结束了!
:下面是方法createRule。这就是创建描述如何绘制特性的符号赋予器的地方
/**
* Helper for createXXXStyle methods. Creates a new Rule containing a Symbolizer tailored to the
* geometry type of the features that we are displaying.
*/
private Rule createRule(Color outlineColor, Color fillColor) {
Symbolizer symbolizer = null;
Fill fill = null;
Stroke stroke = sf.createStroke(ff.literal(outlineColor), ff.literal(LINE_WIDTH));
switch (geometryType) {
case POLYGON:
fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));
symbolizer = sf.createPolygonSymbolizer(stroke, fill, geometryAttributeName);
break;
case LINE:
symbolizer = sf.createLineSymbolizer(stroke, geometryAttributeName);
break;
case POINT:
fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));
Mark mark = sf.getCircleMark();
mark.setFill(fill);
mark.setStroke(stroke);
Graphic graphic = sf.createDefaultGraphic();
graphic.graphicalSymbols().clear();
graphic.graphicalSymbols().add(mark);
graphic.setSize(ff.literal(POINT_SIZE));
symbolizer = sf.createPointSymbolizer(graphic, geometryAttributeName);
}
Rule rule = sf.createRule();
rule.symbolizers().add(symbolizer);
return rule;
}
shapefile特性的几何类型
最后(是的,真的),上面的createRule方法需要知道我们正在处理什么样的特征几何来创建合适的符号化器类。下面是解决这个问题的方法:
/** Retrieve information about the feature geometry */
private void setGeometry() {
GeometryDescriptor geomDesc = featureSource.getSchema().getGeometryDescriptor();
geometryAttributeName = geomDesc.getLocalName();
Class<?> clazz = geomDesc.getType().getBinding();
if (Polygon.class.isAssignableFrom(clazz) || MultiPolygon.class.isAssignableFrom(clazz)) {
geometryType = GeomType.POLYGON;
} else if (LineString.class.isAssignableFrom(clazz)
|| MultiLineString.class.isAssignableFrom(clazz)) {
geometryType = GeomType.LINE;
} else {
geometryType = GeomType.POINT;
}
}
运行程序
下面是显示bc_voting_areas shapefile(包含在 uDig sample data中)的程序,其中选择了一个特性(多边形):
扩展
几何和CRS教程中,我们看到了如何更改MapContent的坐标参考系统。试试以下:
- 修改此应用程序,以便可以更改显示特性的CRS。
- 显示bc_voting_areas shapefile并将CRS更改为EPSG:4326
- 现在尝试使用选择工具。你会发现它不再工作!
看看你是否能找出为什么这个工具不能工作以及如何修复它。
实际上,GeoTools中包含了一些令人惊叹的风格生成代码。在gt-brewer模块上添加一个依赖项,并查看ColorBrewer类。
ColorBrewer 类的工作方式是,首先要求您使用feature collection上的某个分类函数来计算分类;然后,您可以将结果分类传递给color brewer,它将为您生成基于预定义调色板的样式。
图层样式注记
风格是所有关于好看,这一节是一盒蜡笔,学习如何使地图好看是制图学的实践。
实际上,
有时组织会有必须遵循的制图标准。线条究竟有多粗,用什么色度的蓝色来做水。有一个地图标准是一个伟大的时间节省-我们其余的人将不得不有创造性。
不过,我们有一种标准可以帮助我们:样式层描述符(SLD)标准——本文档定义了一个很好的样式数据结构,我们以Java对象的形式捕获了它。如果您在任何地方遇到问题,请查看SLD规范,因为它定义了我们今天要处理的所有思想。
它的核心关注两件事:
1.图层样式注记
涵盖“层”的定义或特性内容的表示。
2.图形符号编码
描述或如何绘制功能
控制呈现过程
这是使用地理工具(或使用开放标准)制作地图的核心。
image.png
如果你想象一个巨大的漏斗,把所有的要素同时扔到你的地图上,这将会有所帮助。这有点像硬币分类机的工作原理——机器的早期阶段会选择一个要素;一旦我们确定我们有什么样的功能,我们将使用该功能来控制实际绘制到不同的位图。最后,我们将收集所有不同的位图(并在其上贴标签)以生成最终的图像
呈现将在以下阶段进行:
- 内容选择:选择和过滤
- 描述:实际图
- 构成:把所有的东西放在一起
第一道防线是FeatureTypeStyle,它利用一个约束来选择要使用什么FeatureType。如果您不介意使用带有名称特性的FeatureType作为一种通配符(因为所有东西都扩展了特性)。
接下来是规则。规则实际上使用Filter(来自Filter教程)来执行对将要绘制的内容的严格检查。除了使用筛选器检查特性属性外,规则还可以检查映射的当前比例。最后,还有另一个规则用于捕获早期规则遗留下来的任何特性。
现在规则已经为我们选择了要使用的特性,我们可以在描述步骤中开始绘图。渲染器将遍历(规则的)符号列表并绘制结果。符号化只是一列按顺序绘制的指令。符号化使用表达式来定义宽度和颜色——允许你在特征的基础上动态生成特征的外观!
唯一没有按顺序绘制的符号是TextSymbolizer,它收集文本标签以供下一步使用
最后在构图步骤中——将所有在描绘过程中绘制的内容挤压在一起形成最终的图像。最后,文本标签从所有层生成并绘制在地图的顶部(注意避免任何文本重叠)。