Java的地理图表

9

有没有人能推荐一个Java组件,可以创建一个漂亮的世界地图图片,并突出显示某些国家(根据一些统计数据)。类似于这张图片:

World map with highlighted countries

希望有类似于Google Geo Charts(但适用于Java)的东西: https://developers.google.com/chart/interactive/docs/gallery/geochart,但在服务器端运行,无需互联网连接。理想情况下,我想给一些国家附加权重,以比例突出它们。

可以是开源或商业软件(只要不是价格荒谬的东西)。


你可以使用d3js和JavaScript引擎。 - user
你期望的输出格式是什么?你希望它是静态的(如光栅图像),还是需要动态/需要能够脚本化鼠标悬停等功能? - Steve Siebert
@SteveSiebert 只是一个静态图像,用于包含在将要打印出来的 .pdf 报告中。因此,大多数图像格式都应该足够。 - AtliB
@user 我担心混淆JavaScript和Java...这似乎是有问题的。 - AtliB
@Atlib 好的,谢谢。让我看看能做出什么。 - Steve Siebert
6个回答

5

我找不到一个Java库来完成你所需要的功能,因此我研究了如何修改SVG样式并将其转换为图像。对于这个任务,我首先想到的是Apache Batik。

我选择使用wikimedia上提供的开源SVG地图(类似于Gregor Opheys的答案),因为这样可以避免版权问题,而且已经准备好了可供国家进行简单CSS修改的模板。(请参阅SVG注释以获取说明)。但有一个小问题,一些在wikimedia上的图像是由Python脚本生成的,这也将CSS样式放入了元素中。如果您想使用这些SVG文件之一,您需要处理它。

令我高兴的是,在Batik维基上有一个非常完美的示例,只需要稍微调整一下就行了。我能够通过进行一些小的语法更改(他们的输出文件名为.jpg,但实际上使用的是PNG转码器)和从项目资源中加载SVG而生成修改后的图像(PNG)。以下是示例代码及我的小更改:

public class MapMaker {

    private final static String MAP_FLAT = "BlankMap-World6-Equirectangular.svg";
    private final static String MAP_ROUND = "WorldMap.svg";

    public static void main(String... args) {

        try {
            // make a Document with the base map 
            String parser = XMLResourceDescriptor.getXMLParserClassName();
            SAXSVGDocumentFactory f = new SAXSVGDocumentFactory(parser);
            Document doc = f.createDocument("http://example.com/stuff",
                    MapMaker.class.getClassLoader().getResourceAsStream(MAP_ROUND));

            // prepare to modify and transcode the document
            SVGDocument sdoc = (SVGDocument) doc;
            Element svgRoot = sdoc.getDocumentElement();
            PNGTranscoder t = new PNGTranscoder();
            TranscoderInput input = new TranscoderInput(doc);

            // find the existing stylesheet in the document
            NodeList stylesList = doc.getElementsByTagName("style");
            Node styleNode = stylesList.item(0);

            // append another stylesheet after the existing one
            SVGStyleElement style = (SVGStyleElement) doc.createElementNS(SVG_NAMESPACE_URI, "style");
            style.setAttributeNS(null, "type", "text/css");
            style.appendChild(doc.createCDATASection(".us {fill: blue;}"));
            styleNode.getParentNode().appendChild(style);

            // transcode the map
            OutputStream ostream = new FileOutputStream("outblue.jpg");
            TranscoderOutput output = new TranscoderOutput(ostream);
            t.transcode(input, output);
            ostream.close();

            // replace the appended stylesheet with another
            SVGStyleElement oldStyle = style;
            style = (SVGStyleElement) doc.createElementNS(SVG_NAMESPACE_URI, "style");
            style.setAttributeNS(null, "type", "text/css");
            style.appendChild(doc.createCDATASection(".us {fill: green;}"));
            styleNode.getParentNode().replaceChild(style, oldStyle);

            // transcode the revised map
            File outFile = new File(System.getProperty("java.io.tmpdir"), "outgreen.png");
            ostream = new FileOutputStream(outFile);
            output = new TranscoderOutput(ostream);
            t.transcode(input, output);
            ostream.close();
            System.out.println("Out File: " + outFile);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

正如我所提到的,这是通过在将SVG转换为所需的光栅图像之前向其添加CSS来实现的。秘密武器是SVGStyleElement,在此处,您可以看到美国变成了绿色。对于任何其他国家,您只需使用它们的2个字母缩写并选择您想要填充的颜色即可。当然,由于它是CSS,您还可以做诸如更改边框颜色或使用背景图像而不是颜色等事情,因此您有很大的灵活性。
我确实需要稍微调整依赖关系,因此为了帮助您跨越此障碍,我将包括我的maven依赖项:
<dependencies>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-parser</artifactId>
        <version>1.7</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-css</artifactId>
        <version>1.7</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-svg-dom</artifactId>
        <version>1.7</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-transcoder</artifactId>
        <version>1.7</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-rasterizer</artifactId>
        <version>1.7</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlgraphics</groupId>
        <artifactId>batik-codec</artifactId>
        <version>1.7</version>
    </dependency>
</dependencies>

如果您使用不同的构建管理器,请告诉我,我可以输出传递依赖树。

2
作为一个起点,你可以轻松地使用SVG世界地图(例如这个)滚动一个组件或者一个函数。在SVG源代码中,保留一个国家名称到路径元素的java.util.Map。 你的函数将接受一组国家名称,并基本上返回地图的SVG字符串。其中,路径的id与集合中的路径匹配时,您将添加一个填充属性和所需的颜色... 下面是代码。您需要下载此文件并将其保存为同一文件夹中的world.svg。然后将要突出显示的国家作为参数传递给它。程序将写入一个名为out.svg的文件,您可以在浏览器中打开它(我使用的是Firefox)。希望它能解决问题。我只尝试从eclipse运行它...
package com.examples.firstbundle;


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class SVGMap {
    private static String prefix = "";
    private static String postfix = "";
    private static Map<String, String> worldMap = new TreeMap<String, String>();

    private final static String pathFormat = "<path id=\"{0}\" d=\"{1}\" fill=\"{2}\" />"; 
    static {

        try {
            List<String> lines = Files.readAllLines(FileSystems.getDefault().getPath("world.svg"), StandardCharsets.UTF_8);

            boolean pathstarted = false;
            for(String line : lines) {
                if(isPath(line)) {
                    pathstarted = true;
                    worldMap.put(getCountry(line), getPath(line));
                } else {
                    if(!pathstarted) {
                        prefix += line;
                    }
                    else {
                        postfix += line;
                    }

                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        Set<String> countries = new TreeSet<String>();
        for(String country : args) {
            countries.add(country);
        }
        FileOutputStream fileOutputStream = new FileOutputStream("out.svg");
        OutputStreamWriter out = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);

        out.append(prefix);
        for(String country : worldMap.keySet()) {
            out.append(MessageFormat.format(pathFormat, country, worldMap.get(country), countries.contains(country) ? "tomato" : "grey"));
        }
        out.append(postfix);
        out.flush();
        out.close();
    }

    private static String getPath(String line) {
        String part = line.split("=")[2];
        String path = part.split("\"")[1];
        return path;
    }

    private static String getCountry(String line) {
        String part = line.split("=")[1];
        String country = part.split("\"")[1];
        return country;
    }

    private static boolean isPath(String line) {
        return line.startsWith("<path");
    }

}

最大的问题在于手动定义路径/坐标需要付出很大的努力...并且让它看起来漂亮。但这正是我倾向于的方向。我本来以为这项工作已经在某个地方完成了! - AtliB
1
@AtliB:只需从我链接的SVG中获取路径/坐标即可。如果时间允许,我会添加一些代码来实现这一点... - Gregor Ophey
我没有意识到 .svg 只是一个 XML 文档,在这种情况下,所有国家都被定义了... 这很酷。 - AtliB

2

2

看一下GeoTools,它可能有你需要的内容。


该项目的网页严重缺少一些截图/示例。 - AtliB

1

你可以使用 GWT 应用来实现。GWT (Google Web ToolKit) 是一个基于 Java 的应用程序制作平台。它提供了与 Google Charts JavaScript API 中相同类型的可视化 API,但是使用的是 Java 代码。请查看此链接以获取基于 Java 的图表 API 和地理图表的实时示例 http://gwt-charts.appspot.com/#geochart。希望这有所帮助 :)


非常接近,但似乎它确实依赖于Google服务器,因此无法离线运行: https://developers.google.com/chart/interactive/faq?csw=1#offline - AtliB

0

我认为对你更好的选择是:

gvSIG 以其用户友好的界面而闻名,能够访问最常见的矢量和栅格格式。它具有广泛的工具,用于处理类似地理信息的数据(查询工具、布局创建、地理处理、网络等)。

来源:gvSIG 维基百科


2
似乎没有API...看起来是一个桌面应用程序。我需要一个组件,可以创建图片格式的地图并将其包含在报告中。 - AtliB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接