Geo chart for java

2019-03-20 05:19发布

问题:

Can anyone recommend a Java component that lets you create a pretty looking image of a world map, highlighting certain countries (based on some statistics). Something similar to this image:

Something similar to Google Geo Charts (but for Java): https://developers.google.com/chart/interactive/docs/gallery/geochart but runs on the server side, without an internet connection. Ideally I'd like to attach weight to a few countries which would highlight them proportionally.

Either open source or commercial (as long as it's not something ridiculously priced).

回答1:

I couldn't find a java library to do what you were looking for, so I looked into taking an SVG and modifying its style, and converting it to an image. First thought that came to my mind for this task: Apache Batik.

I chose to use the open source svg maps available on wikimedia (similar to Gregor Opheys answer) because not does it make it so you're not concerned about licensing, it also is already ready-made for simple CSS modifications by country. (See the SVG comments for instructions). There is one hitch, some of those image on wikimedia were generated by a python script, which puts the CSS styling also in the elements. If you want to use one of these SVG files, you'll have to handle for that.

To my happy surprise, there was almost a perfect example on the batik wiki, needing just a couple tweaks. I was able to produce a modified image (png) with one small syntax change (their outfile file is named .jpg but its using a png transcoder) and a small change to load the svg from the project resources instead of disk. Below is the example code with my minor changes:

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();
        }
    }
}

As I mentioned, the way this works is by adding CSS to the SVG prior to converting it to the raster image you need. The secret sauce is the SVGStyleElement, here you can see that the United States is turned green. For any other country, you would simply use their 2-letter digraph and chose the color you would like to fill. Of course, since it's CSS, you can also do things like change the border colors or use a background image instead of a color, so you have a lot of flexibility there.

I did have to play with the dependencies a bit, so to get you past this hurdle, I'll include my maven dependencies:

<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>

If you use a different build manager, let me know, and I can dump the transitive dependency tree.



回答2:

Have a look at GeoTools, it may have what you need.



回答3:

Haven't used it and not sure if it is appropriate for your needs but you can have a look at Nasa WorldWind. It features an SDK and it seems like that there is an off-line mode available.

Check :

  • http://worldwind.arc.nasa.gov/java/ and http://goworldwind.org/demos/

  • http://goworldwind.org/examples/#network (for offline mode)



回答4:

Well, you can make a GWT App for that. GWT(Google Web ToolKit) is a Java based app making plateform. It provides same kind of visualization APIs as you see in Google Charts javascript API but in java code.Checkout this link for Java based Chart API and live example of geo chart in Java Code http://gwt-charts.appspot.com/#geochart . Hope this helps :)



回答5:

As a start you could easily roll a component or rather a function like that yourself using an SVG worldmap (e.g this). Keep a java.util.Map of country names to path elements as given in the SVG source. Your function would take a set of country names and essentially return the svg for the map as a string. Where the id of the path matches one in the set you would add a fill attribute with the color you want ...

Here is the code. You will need to download this and have it as world.svg in the same folder. Then pass in the countries you want highlighted as arguments. The program will write a file out.svg which you can open in a browser (I used Firefox). I hope it does the trick. I only tried running it from 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");
    }

}


回答6:

I think better for you :

gvSIG is known for having a user-friendly interface, being able to access the most common formats, both vector and raster ones. It features a wide range of tools for working with geographic-like information (query tools, layout creation, geoprocessing, networks, etc.)

source : gvSIG Wikipedia