LoginSignup
11
6

More than 1 year has passed since last update.

PLATEAUのCityGMLからMVTを作成する作業の記録

Posted at

1.概要

インディゴ株式会社さんの下記のエントリを参考に、PLATEAUのCityGMLから建物のMapbox Vector Tile(MVT)を作ってみました。
その作業記録です。

2.CityGMLからGeojsonを生成

PlateauのCityGMLを読み込み、Geojsonを生成するユーティリティを作成しました。
CityGMLの読み込みはcitygml4j、geojson生成はgsonを使用しています。
geojsonは、建物のLOD0FootPrintからgeometryを作成し、GenericAttributeからpropertiesを作成しています。

CiryGMLのフォルダを指定してCityGMLUtilを実行すると、同じフォルダにgeojsonファイルが生成されます。

CityGMLUtil.java

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.citygml4j.CityGMLContext;
import org.citygml4j.builder.jaxb.CityGMLBuilder;
import org.citygml4j.builder.jaxb.CityGMLBuilderException;
import org.citygml4j.model.citygml.CityGML;
import org.citygml4j.model.citygml.CityGMLClass;
import org.citygml4j.model.citygml.building.Building;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.citygml.core.CityModel;
import org.citygml4j.model.citygml.core.CityObjectMember;
import org.citygml4j.model.citygml.generics.AbstractGenericAttribute;
import org.citygml4j.model.citygml.generics.IntAttribute;
import org.citygml4j.model.citygml.generics.MeasureAttribute;
import org.citygml4j.model.citygml.generics.StringAttribute;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurface;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import org.citygml4j.model.gml.geometry.primitives.DirectPositionList;
import org.citygml4j.model.gml.geometry.primitives.Exterior;
import org.citygml4j.model.gml.geometry.primitives.LinearRing;
import org.citygml4j.model.gml.geometry.primitives.Polygon;
import org.citygml4j.model.gml.geometry.primitives.SurfaceProperty;
import org.citygml4j.xml.io.CityGMLInputFactory;
import org.citygml4j.xml.io.reader.CityGMLReadException;
import org.citygml4j.xml.io.reader.CityGMLReader;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class CityGMLUtil {

    public static void readCityGML(File f,List<Map<String,Object>> list) throws CityGMLBuilderException, CityGMLReadException{
        CityGMLContext ctx = CityGMLContext.getInstance();
        CityGMLBuilder builder = ctx.createCityGMLBuilder();
        CityGMLInputFactory in = builder.createCityGMLInputFactory();
        CityGMLReader reader = in.createCityGMLReader(f);
        while (reader.hasNext()) {
            CityGML citygml = reader.nextFeature();
            if (citygml.getCityGMLClass() == CityGMLClass.CITY_MODEL) {
                CityModel cityModel = (CityModel)citygml;
                for (CityObjectMember cityObjectMember : cityModel.getCityObjectMember()) {
                    AbstractCityObject cityObject = cityObjectMember.getCityObject();
                    if (cityObject.getCityGMLClass() == CityGMLClass.BUILDING){
                        Building b=(Building)cityObject;
                        if(b.getMeasuredHeight()!=null){
                            list.add(createFeature(b));
                        }
                    }
                }
            }
        }
        reader.close();
    }

    private static Map<String,Object> createFeature(Building b){
        Map<String,Object> ret=new HashMap<>();
        ret.put("type","Feature");
        Map<String,Object> geom=new HashMap<>();
        Map<String,Object> prop=new HashMap<>();
        ret.put("geometry",geom);
        ret.put("properties",prop);
        geom.put("type","Polygon");
        MultiSurfaceProperty msp=b.getLod0FootPrint();
        MultiSurface ms=msp.getMultiSurface();
        List<SurfaceProperty> spl=ms.getSurfaceMember();
        Polygon pp=(Polygon)spl.get(0).getGeometry();
        Exterior ex=(Exterior)pp.getExterior();
        LinearRing lr=(LinearRing)ex.getRing();
        DirectPositionList dpl=(DirectPositionList)lr.getPosList();
        List<Double> dl=dpl.toList3d();
        List<double[]> tmp=new ArrayList<>();
        double dem=0.0;
        for(int i=0;i<dl.size();i=i+3){
            Double d01=dl.get(i);
            Double d02=dl.get(i+1);
            Double d03=dl.get(i+2);
            tmp.add(new double[]{d02,d01});
            dem=d03;
        }
        List<List<double[]>> c=new ArrayList<>();
        c.add(tmp);

        geom.put("coordinates",c);
        prop.put("measuredHeight",b.getMeasuredHeight().getValue());
        List<AbstractGenericAttribute> ll=b.getGenericAttribute();
        for(AbstractGenericAttribute at : ll){
            if(at instanceof StringAttribute){
                StringAttribute st=(StringAttribute)at;
                prop.put(st.getName(), st.getValue());
            }else if(at instanceof MeasureAttribute){
                MeasureAttribute st=(MeasureAttribute)at;
                prop.put(st.getName(), st.getValue().getValue());
            }else if(at instanceof IntAttribute){
                IntAttribute st=(IntAttribute)at;
                prop.put(st.getName(), st.getValue());
            }
        }
        prop.put("dem", dem);
        return ret;
    }

    public static void main(String[] args){
        File in=new File(args[0]); //CityGMLのディレクトリ
        try {
            Gson gson=new GsonBuilder().setPrettyPrinting().create();
            for(File f : in.listFiles()){
                if(f.isDirectory())continue;
                if(f.getName().toLowerCase().endsWith(".gml")){
                    System.out.println(f.getName());
                    Map<String,Object> root=new HashMap<>();
                    root.put("type", "FeatureCollection");
                    List<Map<String,Object>> list=new ArrayList<>();
                    root.put("features", list);
                    readCityGML(f,list);
                    try {
                        File out=new File(f.getAbsolutePath().replace(".gml", ".geojson"));
                        BufferedWriter bw=new BufferedWriter(new FileWriter(out));
                        bw.write(gson.toJson(root));
                        bw.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        } catch (CityGMLBuilderException e) {
            e.printStackTrace();
        } catch (CityGMLReadException e) {
                e.printStackTrace();
        }
    }
}

3.Mapbox Vector Tile生成

生成したgeojsonから、tippecanoeを使用してMVTを生成します。

tippecanoeについては、インディゴ株式会社さんの先のエントリの他、@frogcatさんの下記エントリを参考にしました。

geojsonのディレクトリで下記のコマンドを実行すると、指定したフォルダ(下記ではpbf)にMVTデータ(レイヤー名はbldg)が出力されます。

tippecanoe --no-tile-compression -ad -an -Z12 -z16 -e ../pbf -l bldg -ai *.geojson

4.成果物

Plateauの『3D都市モデル(Project PLATEAU)大阪市(2020年度)』をMVTに変換した結果です。
建物外郭線(LOD0)を建物高さで立ち上げたLOD1相当のデータですが、3Dtilesに比べて軽く動作する感じです。
なお、大阪市のMVTタイルの容量は、ズームレベル12~16で、120MBでした。

Failed to fetch -- ERR_CONTENT_DECODING_FAILED · Issue #8099 · mapbox_mapbox-gl-js - Google Chrome 2021_06_06 19_55_46 (2).png

plateau-tokyo23ku-building-mvt-2020 - Google Chrome 2021_06_06 22_12_49 (2).png

5.最後に

PLATEAUはとても興味深いのですが、3DTilesはジオイドの関係でdeck.gl、maplibre、mapboxでは使いづらかったので、何かうまい方法はないかなと考えていたところでした。
Twitterでインディゴ株式会社さんのGithubを知り、「こんな事ができるんだ」と目からうろこでした。素晴らしい情報をありがとうございます!
試しに大阪市データからMVTを作ってみると、非常に簡単に作れたので、ちょっと色々と試してみたくなりました。
また、国土基盤情報の建物外郭線と兵庫県全域数値地形図のようなDSM・DEMがあれば、同じようなLOD1相当の建物データが作れそうに感じました。

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6