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