2011년 7월 28일 목요일

[GeoTools]Graduated Color Renderer 생성

이번 시간에는 GeoTools 및  GeoTools의 Extension 중 하나인 Brewer를 이용하여 Graduated Color Renderer를 생성하는 샘플 코드를 작성해 보도록 하겠습니다.

▣ 학습 내용
 - Shapefile DataStore  및 Shapefile 불러오기
 - Function 및  ColorBrewer  사용하기
 - Style 생성 및 SLD XML로 내보내기
 - MapContext  사용하여 지도 보기

▣ Reference
GeoTools Brewer Extension
 - http://docs.geotools.org/latest/userguide/extension/brewer/index.html#

Colorbrewer: Color Advice for Maps : http://colorbrewer2.org/
 - Colorbrewer에서 사용되는 Palette Name들입니다.
 - Diverging: PuOr, BrBG, PRGn, PiYG, RdBu, RdGy, RdYlBu, Spectral, RdYlGn
 - Qualitative: Set1, Pastel1, Set2, Pastel2, Dark2, Set3, Paired, Accents,
 - Sequential: YlGn, YlGnBu, GnBu, BuGn, PuBuGn, PuBu, BuPu, RdPu, PuRd, OrRd, YlOrRd, YlOrBr, Purples, Blues, Greens, Oranges, Reds, Grays,

◎ Ranged ClassificationFunction
 - GeoTools에 지원하는 ClassificationFunction은 다음과 같습니다.
 - JenksNaturalBreaksFunction(Jenks), EqualIntervalFunction(EqualInterval), StandardDeviationFunction(StandardDeviation), QuantileFunction(Quantile )

◎ ArcObjects
 - ArcObjects에서는 IClassifyGEN과  IClassBreaksRenderer를 이용하여 주제도를 작성할 수 있습니다.
IClassBreaksRendererIClassifyGEN

▣ Sample Dataset
 - [Shapefile] Sample Datasets for Spatial Statistics Analysis

▣ Preview
◎ Map
◎ SLD to XML
 - StyledLayerDescriptor

▣ Sample Code
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.transform.TransformerException;

import org.geotools.brewer.color.BrewerPalette;
import org.geotools.brewer.color.ColorBrewer;
import org.geotools.brewer.color.StyleGenerator;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFinder;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.function.RangedClassifier;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.styling.FeatureTypeConstraint;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.SLDTransformer;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.StyledLayerDescriptor;
import org.geotools.styling.UserLayer;
import org.geotools.swing.JMapFrame;
import org.geotools.util.logging.Logging;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Function;

public class ColorBrewerMap {
    static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
    static StyleFactory sf = CommonFactoryFinder.getStyleFactory(null);

    static final Logger LOGGER = Logging.getLogger(ColorBrewerMap.class);
    public static void main(String[] args) throws IOException {
        String sourceFolder = "C:/TEMP/spatialstatistics";
        DataStore srcDataStore = getShapefileDataStore(sourceFolder);
        SimpleFeatureSource srcSfs = srcDataStore.getFeatureSource("seoul_hexagon_1000");

        // get featurecollection
        SimpleFeatureCollection srcFc = srcSfs.getFeatures(Filter.INCLUDE);
        String propertyName = "EVE_CNT";
        String functionName = "EqualInterval";
        int numClass = 5;
        String paletteName = "YlGn";

        // prepare classifier
        Function function = ff.function(functionName, ff.property(propertyName), ff.literal(numClass));

        RangedClassifier classifier = (RangedClassifier) function.evaluate(srcFc);

        // prepare ColorBrewer
        ColorBrewer colorBrewer = ColorBrewer.instance();
        BrewerPalette brewerPalette = colorBrewer.getPalette(paletteName);
        Color[] colors = brewerPalette.getColors(classifier.getSize());

        // create style
        SimpleFeatureType featureType = srcFc.getSchema();
        Stroke outline = sf.createStroke(ff.literal(Color.GRAY), ff.literal(1.0f), ff.literal(1.0f));
        FeatureTypeStyle fts = StyleGenerator.createFeatureTypeStyle(classifier,
                ff.property(propertyName), colors, featureType.getTypeName(),
                featureType.getGeometryDescriptor(), StyleGenerator.ELSEMODE_IGNORE,
               1.0, outline);
        Style style = sf.createStyle();
        style.featureTypeStyles().add(fts);

        // export sld as XML
        printStyleToSLD(style);
      
        // show map
        MapContext mapContext = new DefaultMapContext();
        mapContext.setTitle(featureType.getTypeName() + "-" + function.getName());
        mapContext.addLayer(srcFc, style);

        JMapFrame mapFrame = new JMapFrame(mapContext);
        mapFrame.setSize(800, 600);
        mapFrame.enableStatusBar(true);
        mapFrame.enableToolBar(true);
        mapFrame.setVisible(true);
    }
  
    static void printStyleToSLD(Style style) {
        UserLayer layer = sf.createUserLayer();
        layer.setLayerFeatureConstraints(new FeatureTypeConstraint[] { null });
        layer.setName(style.getName());
        layer.addUserStyle(style);

        StyledLayerDescriptor sld = sf.createStyledLayerDescriptor();
        sld.addStyledLayer(layer);

        SLDTransformer styleTransform = new SLDTransformer();
        try {
            styleTransform.setIndentation(4);
            styleTransform.setEncoding(Charset.forName("UTF-8"));
            String xml = styleTransform.transform(sld);

            System.out.println(xml);
        } catch (TransformerException e) {
            LOGGER.log(Level.FINER, e.getMessage(), e);
        }

    }

    static DataStore getShapefileDataStore(String folder) throws IOException {
        Map<String, Serializable> params = new HashMap<String, Serializable>();
        try {
            File file = new File(folder);

            params.put("url", file.toURI().toURL());
            params.put("create spatial index", Boolean.FALSE);
            params.put("charset", "CP949");  // or EUC-KR

            return DataStoreFinder.getDataStore(params);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        return null;
    }
}
▣ 심화학습
◎  Normalization Field 사용
만약 ArcGIS에서처럼 Normalization field를 사용하려면 어떻게 해야 할까요?


Function function = ff.function(functionName, ff.property(propertyName), ff.literal(numClass));

에서 아래와 같이  Normalization field와 Divide를 함께 사용하면 됩니다.

String normalProeprtyName = "GEOM_AREA";
Divide divide = ff.divide(ff.property(propertyName), ff.property(normalProeprtyName));
Function function = ff.function(functionName, divide, ff.literal(numClass));

◎  StyleGenerator.createFeatureTypeStyle () 을 직접 구현해 보기