我在WorldWind项目中,遇到过这样一个问题。加载本地影像时,由于投影转换的问题,有些地理区域存在挺明显的误差情况。具体一个例子就是,我在GoogleEarth中画了一个区域,另存为kml文件,在WorldWind中导入时一些地理标志明显不重合。我为了项目展示时的整齐一致,所以采用在WorldWind中画取路径区域,并导出为kml文件。也许这种方法并不见得多可取,不过也是写出来留作参考,为了以后也许能够用到的地方。
通过分析源代码可以发现,在src->gov.nasa.worldwindx.examples中的LineBuilder.java文件可以在球体上勾画路径并能够把相应点的坐标输出,再结合src->gov.nasa.worldwindx.examples.kml中的ExportKML.java文件,就可以达到我们需要的目的。
一、创建自己的WWJLineBuilder.java文件
package esi.control;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.List;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.AVListImpl;
import gov.nasa.worldwind.event.PositionEvent;
import gov.nasa.worldwind.event.PositionListener;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.Polyline;
public class WWJLineBuilder extends AVListImpl
{
private final WorldWindow wwd;
private boolean armed = false;
private List positions;
private final RenderableLayer layer;
private final Polyline line;
private boolean active = false;
public WWJLineBuilder(final WorldWindow wwd, RenderableLayer lineLayer, Polyline polyline)
{
this.wwd = wwd;
positions=new ArrayList();
if (polyline != null)
{
this.line = polyline;
}
else
{
this.line = new Polyline();
this.line.setFollowTerrain(true);
}
this.layer = lineLayer != null ? lineLayer : new RenderableLayer();
this.layer.addRenderable(this.line);
this.wwd.getModel().getLayers().add(this.layer);
this.wwd.getInputHandler().addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent mouseEvent)
{
if (armed && mouseEvent.getButton() == MouseEvent.BUTTON1)
{
if (armed && (mouseEvent.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
{
if (!mouseEvent.isControlDown())
{
active = true;
addPosition();
}
}
mouseEvent.consume();
}
}
public void mouseReleased(MouseEvent mouseEvent)
{
if (armed && mouseEvent.getButton() == MouseEvent.BUTTON1)
{
if (positions.size() == 1)
removePosition();
active = false;
mouseEvent.consume();
}
}
public void mouseClicked(MouseEvent mouseEvent)
{
if (armed && mouseEvent.getButton() == MouseEvent.BUTTON1)
{
if (mouseEvent.isControlDown())
removePosition();
mouseEvent.consume();
}
}
});
this.wwd.getInputHandler().addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent mouseEvent)
{
if (armed && (mouseEvent.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
{
if (active)
mouseEvent.consume();
}
}
});
this.wwd.addPositionListener(new PositionListener()
{
public void moved(PositionEvent event)
{
if (!active)
return;
if (positions.size() == 1)
addPosition();
else
replacePosition();
}
});
}
/**
* Returns the layer holding the polyline being created.
*/
public RenderableLayer getLayer()
{
return this.layer;
}
/**
* Returns the layer currently used to display the polyline.
*/
public Polyline getLine()
{
return this.line;
}
/**
* Removes all positions from the polyline.
*/
public void clear()
{
while (this.positions.size() > 0)
this.removePosition();
}
/**
* Identifies whether the line builder is armed.
*/
public boolean isArmed()
{
return this.armed;
}
/**
* Arms and disarms the line builder. When armed, the line builder monitors user input and builds the polyline in
* response to the actions mentioned in the overview above. When disarmed, the line builder ignores all user input.
*/
public void setArmed(boolean armed)
{
this.armed = armed;
}
private void addPosition()
{
Position curPos = this.wwd.getCurrentPosition();
if (curPos == null)
return;
this.positions.add(curPos);
this.line.setPositions(this.positions);
this.firePropertyChange("wwjExportKML.AddPosition", null, curPos);
this.wwd.redraw();
}
private void replacePosition()
{
Position curPos = this.wwd.getCurrentPosition();
if (curPos == null)
return;
int index = this.positions.size() - 1;
if (index < 0)
index = 0;
Position currentLastPosition = this.positions.get(index);
this.positions.set(index, curPos);
this.line.setPositions(this.positions);
this.firePropertyChange("wwjExportKML.ReplacePosition", currentLastPosition, curPos);
this.wwd.redraw();
}
private void removePosition()
{
if (this.positions.size() == 0)
return;
Position currentLastPosition = this.positions.get(this.positions.size() - 1);
this.positions.remove(this.positions.size() - 1);
this.line.setPositions(this.positions);
this.firePropertyChange("wwjExportKML.RemovePosition", currentLastPosition, null);
this.wwd.redraw();
}
}
这个文件主要作用是勾画路径,下面我们创建WWJLinePanel.java文件,主要是为了操作面板的设计以及编写导出kml函数的接口。
主要代码如下:
package esi.control;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.render.Path;
import gov.nasa.worldwind.render.Polygon;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwindx.examples.kml.KMLDocumentBuilder;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.CompoundBorder;
import javax.swing.border.TitledBorder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
public class WWJLinePanel extends JPanel
{
private final WorldWindow wwd;
private final WWJLineBuilder wwjExportKML;
private JButton newButton;
private JButton pauseButton;
private JButton endButton;
private JButton exportButton;
private JLabel[] pointLabels;
/**
* 构造函数
*/
public WWJLinePanel(WorldWindow wwd, WWJLineBuilder wwjExportKML)
{
super(new BorderLayout());
this.wwd = wwd;
this.wwjExportKML = wwjExportKML;
this.makePanel(new Dimension(100,50));
}
/**
* 构造面板
*/
private void makePanel(Dimension size)
{
JPanel buttonPanel = new JPanel(new GridLayout(2, 2, 5, 5));
newButton = new JButton("New");
newButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
wwjExportKML.clear();
wwjExportKML.setArmed(true);
pauseButton.setText("Pause");
pauseButton.setEnabled(true);
endButton.setEnabled(true);
newButton.setEnabled(false);
((Component) wwd).setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}
});
buttonPanel.add(newButton);
newButton.setEnabled(true);
pauseButton = new JButton("Pause");
pauseButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
wwjExportKML.setArmed(!wwjExportKML.isArmed());
pauseButton.setText(!wwjExportKML.isArmed() ? "Resume" : "Pause");
((Component) wwd).setCursor(Cursor.getDefaultCursor());
}
});
buttonPanel.add(pauseButton);
pauseButton.setEnabled(false);
endButton = new JButton("End");
endButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent actionEvent)
{
wwjExportKML.setArmed(false);
newButton.setEnabled(true);
pauseButton.setEnabled(false);
pauseButton.setText("Pause");
endButton.setEnabled(false);
((Component) wwd).setCursor(Cursor.getDefaultCursor());
}
});
buttonPanel.add(endButton);
endButton.setEnabled(false);
exportButton=new JButton("Export");
exportButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WExportKML();
}
});
buttonPanel.add(exportButton);
exportButton.setEnabled(true);
JPanel pointPanel = new JPanel(new GridLayout(0, 1, 0, 10));
pointPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
this.pointLabels = new JLabel[20];
for (int i = 0; i < this.pointLabels.length; i++)
{
this.pointLabels[i] = new JLabel("");
pointPanel.add(this.pointLabels[i]);
}
// Add the buttons, scroll bar and inner panel to a titled panel that will resize with the main window.
JPanel outerPanel = new JPanel(new BorderLayout());
outerPanel.setBorder(
new CompoundBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9), new TitledBorder("Line")));
outerPanel.setToolTipText("Line control and info");
outerPanel.add(buttonPanel, BorderLayout.NORTH);
this.add(outerPanel, BorderLayout.CENTER);
}
/**
* 定义路径并将所画点置入该路径
*/
private Path makePath(){
Path path=new Path();
List positions=new ArrayList();
// int i=0;
for (Position pos : wwjExportKML.getLine().getPositions())
{
/*if (i == this.pointLabels.length)
break;*/
positions.add(Position.fromDegrees(pos.getLatitude().getDegrees(), pos.getLongitude().getDegrees(),0));
};
path.setPositions(positions);
return path;
}
/**
* 导出路径
*/
public void WExportKML(){
try {
// Create a StringWriter to collect KML in a string buffer
Writer stringWriter = new StringWriter();
// Create a document builder that will write KML to the StringWriter
KMLDocumentBuilder kmlBuilder = new KMLDocumentBuilder(stringWriter);
// Export the objects
kmlBuilder.writeObjects(makePath());
kmlBuilder.close();
// Get the exported document as a string
String xmlString = stringWriter.toString();
// Set up a transformer to pretty-print the XML
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
// Write the pretty-printed document to stdout
transformer.transform(new StreamSource(new StringReader(xmlString)), new StreamResult(System.out));
} catch (Exception e) {
String message = Logging.getMessage("generic.ExceptionAttemptingToWriteXml", e.toString());
Logging.logger().severe(message);
e.printStackTrace();
}
}
}
再然后就需要在WWJMFS.java 文件中进行调用,代码如下,
WWJLineBuilder wlb=new WWJLineBuilder(wwd, null, null);
WWJLinePanel wlp=new WWJLinePanel(wwd, wlb);
this.getContentPane().add(wlp,BorderLayout.PAGE_END);
单击导出按钮,就可以在控制台打印出一段kml代码,复制粘贴到一个新文件中即可。
注:由于该文章仅供测试用,所以就没有进行细节的处理。
1、在实例中,我们仅仅是路径的确定,向其他形状(如多边形)则需要更改代码
/**
* 定义多边形并将所画点置入该多边形
*/
private Polygon makePoly(){
Polygon poly=null;
List positions=new ArrayList();
for (Position pos : wwjExportKML.getLine().getPositions())
{
positions.add(Position.fromDegrees(pos.getLatitude().getDegrees(), pos.getLongitude().getDegrees(),0));
};
poly=new Polygon(positions);
return poly;
}
然后在WExportKml方法中更改部分代码如下
// Export the objects
kmlBuilder.writeObjects(makePoly());
kmlBuilder.close();
即可以画出多边形。本来想采用的是测量工具中选择的形式,不过涉及代码太复杂,作为测试用这种方法即可,所以就没有多做深究。
2、在WExportKml方法中输出的信息是在控制台中,也可以直接打印出文件。在该方法中,// Get the exported document as a string
String xmlString = stringWriter.toString();
这个xmlString字符串即为所需kml全部代码,后面的那些代码主要作用是以一种结构清晰的格式去输出kml代码。如果仅是一段字符串,是可以很简单的在一个文件中进行输出。如果想在一个文件中也采用结构清晰的格式去输出,这方面我还没有很清晰的思路。如果大家有什么好的方法,还望赐教。