Skip to content

Instantly share code, notes, and snippets.

@TetsuyaKimotsuki
Last active May 31, 2022 01:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TetsuyaKimotsuki/0156c511e3217edf58beb206633308f8 to your computer and use it in GitHub Desktop.
Save TetsuyaKimotsuki/0156c511e3217edf58beb206633308f8 to your computer and use it in GitHub Desktop.
Leafletにあれこれ描く
license: mit

Leaflet.Drawで作図しGeoJSONとしてexportできると何か使えるかな、と。

できること

  • オブジェクトが置ける
  • それぞれのオブジェクトのポップアップダイアログからメモを入力できる
  • メモやその他の属性をGeoJsonのpropertiesに持たせてexportできる
  • exportしたGeoJsonファイルをimportできる(これが大変やった…)

参考にさせて頂いたサイト

地理院地図の[機能]→[ツール]→[作図・ファイル]とたどっていくと[ファイルから読み込み]というボタンが現れます。ここからエクスポートしたGeoJSONデータを選択して[読込を開始]すると、地理院地図上に作図データが展開されます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>地図上に図形を描画する</title>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<!-- Leaflet.draw0.4.9がv1.0.xまでしか対応していないので -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet-src.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.3/leaflet.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw-src.js"></script>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.4.9/leaflet.draw-src.css' />
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.5.0/Control.MiniMap.js"></script>
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/leaflet-minimap/3.5.0/Control.MiniMap.css' />
<style type="text/css">
#map {
z-index: 0;
height: 445px;
}
.actions {
display: inline-block;
cursor: pointer;
text-decoration: none;
color: black;
font-family: 'Helvetica Neue';
font-size: 12px;
background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%);
background: linear-gradient(to bottom, #fff 0%, #f0f0f0 100%);
border: 1px solid #ccc;
box-shadow: 0 -1px 0 rgba(255, 255, 255, 1) inset;
margin: 0.3em 0;
padding: 1em;
}
.input-file {
position: relative;
display: inline-block;
}
.input-file input[type="file"] {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
}
.notes {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div id="map"></div>
<div class='actions' id='delete'>Delete Features</div>
<a href='#' class='actions' id='export'>Export Features</a>
<!-- input type="file" のデザイン変更は右参照 http://var.blog.jp/archives/57292423.html -->
<div class="input-file">
<span class="actions">Import Features</span>
<input type="file" id='import'>
</div>
<script type="text/javascript">
// 地図レイヤの準備
var t_pale = new L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', {
attribution: "<a href='http://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html' target='_blank'>国土地理院</a>"
});
var t_std = new L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
attribution: "<a href='http://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html' target='_blank'>国土地理院</a>"
});
var t_ort = new L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/ort/{z}/{x}/{y}.jpg', {
attribution: "<a href='http://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html' target='_blank'>国土地理院</a>"
});
var t_pale_mini = new L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png');
var map = L.map('map', {
center: [34.816702, 135.497171],
zoom: 14,
zoomControl: true,
layers: [t_pale]
});
var miniMap = new L.Control.MiniMap(t_pale_mini, { toggleDisplay: true }).addTo(map);
var baseLayer = {
"地理院地図 淡色": t_pale,
"地理院地図 標準": t_std,
"地理院地図 オルソ": t_ort,
};
L.control.layers(baseLayer, null, {
collapsed: true
}).addTo(map);
L.control.scale({
imperial: false,
maxWidth: 200
}).addTo(map);
// 描画レイヤの初期化
var drawnItems = new L.FeatureGroup().addTo(map);
var drawControl = new L.Control.Draw({
draw: {
circle: {
feet: false
},
},
edit: {
featureGroup: drawnItems,
},
}).addTo(map);
// 何かを描画した際の生成やら設定やら
map.on(L.Draw.Event.CREATED, function (e) {
// featureのポップアップで値を入力する方法は下記を参考にしました
// https://gis.stackexchange.com/questions/202966/leaflet-popups-preserving-user-input-on-close-reopen
// geojsonに落とす際にpropertiesに値を保持する方法は下記を参考にしました
// https://stackoverflow.com/questions/35760126/leaflet-draw-not-taking-properties-when-converting-featuregroup-to-geojson
drawnItems.addLayer(e.layer);
e.layer.feature = e.layer.feature || {};
e.layer.feature.properties = e.layer.feature.properties || {};
e.layer.feature.properties.note = e.layer.feature.properties.note || "";
e.layer.feature.type = "Feature";
popup = e.layer.bindPopup("");
setFeatureProperties(e.layer);
popup.on("popupopen", function (p) {
$('#note_' + p.target._leaflet_id).attr('value', p.target.feature.properties.note).focus();
});
popup.on("popupclose", function (p) {
p.target.feature.properties.note = $('#note_' + p.target._leaflet_id).val();
});
});
map.on(L.Draw.Event.EDITED, function (e) {
e.layers.eachLayer(function (layer) {
setFeatureProperties(layer);
});
});
// 描画物の情報を計算し保持するメソッド(以下を参考にしました)
// http://leaflet.github.io/Leaflet.draw/docs/examples/popup.html
var setFeatureProperties = function (layer) {
// 線と多角形と四角形
if (layer instanceof L.Polyline) {
var latlngs = layer._defaultShape ? layer._defaultShape() : layer.getLatLngs();
if (latlngs.length >= 2) {
var distance = 0;
for (var i = 0; i < latlngs.length - 1; i++) {
distance += latlngs[i].distanceTo(latlngs[i + 1]);
}
layer.feature.properties.distance = distance.toFixed(2) + " m"; // ex. distance 3728.81 m
}
layer.feature.properties.drawtype = L.Draw.Polyline.TYPE;
}
// 多角形と四角形
if (layer instanceof L.Polygon) {
var latlngs = layer._defaultShape ? layer._defaultShape() : layer.getLatLngs();
var area = L.GeometryUtil.geodesicArea(latlngs);
layer.feature.properties.area = L.GeometryUtil.readableArea(area, true); // ex. area 174.19 ha
layer.feature.properties.drawtype = L.Draw.Polygon.TYPE;
}
// 四角形
if (layer instanceof L.Rectangle) {
layer.feature.properties.drawtype = L.Draw.Rectangle.TYPE;
}
// 円
if (layer instanceof L.Circle) {
layer.feature.properties.radius = layer.getRadius().toFixed(2) + " m"; // ex. radius 1097.02 m
layer.feature.properties.drawtype = L.Draw.Circle.TYPE;
}
// マーカー
if (layer instanceof L.Marker) {
layer.feature.properties.drawtype = L.Draw.Marker.TYPE;
}
// popup時の表示内容の差し替え
var contents = "";
for (var key in layer.feature.properties) {
if (key != 'note' && key != 'drawtype') {
contents = contents + key + " " + layer.feature.properties[key] + "<br />";
}
}
contents += "note <input type='text' class='notes' id='note_" + layer._leaflet_id + "' value=''>";
layer.setPopupContent(contents);
};
// geoJSONのdeleteボタン有効化
document.getElementById('delete').onclick = function (e) {
drawnItems.clearLayers();
}
// geoJSONのexportボタン有効化
document.getElementById('export').onclick = function (e) {
// Extractions GeoJson from featureGroup
var geojson = drawnItems.toGeoJSON();
// Stringify the GeoJson
var convertedData = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(geojson));
// Create export
document.getElementById('export').setAttribute('href', 'data:' + convertedData);
document.getElementById('export').setAttribute('download', 'data.geojson');
}
// geoJSONのimportボタン有効化
document.getElementById('import').onchange = function (e) {
var reader = new FileReader();
reader.onload = function () {
var features;
try {
features = JSON.parse(reader.result).features;
}
catch (ex) {
console.log("Imported file is not a JSON file.");
}
// importするfeatureをleaflet.drawで描画したかのように差し込む
for (var feature of features) {
if (feature.type == 'Feature') {
// 線
if (feature.geometry.type == 'LineString') {
var latlngs = [];
for (var point of feature.geometry.coordinates) {
latlngs.push(L.latLng(point[1], point[0]));
}
var handler = drawControl._toolbars.draw._modes.polyline.handler;
var layer = new L.Polyline(latlngs, handler.options.shapeOptions);
layer.feature = feature;
L.Draw.Feature.prototype._fireCreatedEvent.call(handler, layer);
}
else if (feature.geometry.type == 'Polygon') {
if (feature.properties.drawtype == L.Draw.Rectangle.TYPE) {
// 四角形
var handler = drawControl._toolbars.draw._modes.rectangle.handler;
var corner1 = L.latLng(feature.geometry.coordinates[0][0][1], feature.geometry.coordinates[0][0][0]);
var corner2 = L.latLng(feature.geometry.coordinates[0][2][1], feature.geometry.coordinates[0][2][0]);
var layer = new L.Rectangle(new L.LatLngBounds(corner1, corner2), handler.options.shapeOptions);
layer.feature = feature;
L.Draw.SimpleShape.prototype._fireCreatedEvent.call(handler, layer);
}
else {
// 多角形
var rings = [];
for (var ring of feature.geometry.coordinates) {
var latlngs = [];
for (var point of ring) {
latlngs.push(L.latLng(point[1], point[0]));
}
latlngs.pop(); // geoJsonのPolygonでは、最後に先頭と同じ点が入ってくるので削る
rings.push(latlngs);
}
var handler = drawControl._toolbars.draw._modes.polygon.handler;
var layer = new L.Polygon(rings, handler.options.shapeOptions);
layer.feature = feature;
L.Draw.Feature.prototype._fireCreatedEvent.call(handler, layer);
}
}
else if (feature.geometry.type == 'Point') {
var latlng = L.latLng(feature.geometry.coordinates[1], feature.geometry.coordinates[0]);
if (feature.properties.drawtype == L.Draw.Circle.TYPE) {
// 円
var handler = drawControl._toolbars.draw._modes.circle.handler;
var radius = parseFloat(feature.properties.radius); //" m"が勝手にとれるとは…
var layer = new L.Circle(latlng, radius, handler.options.shapeOptions);
layer.feature = feature;
L.Draw.SimpleShape.prototype._fireCreatedEvent.call(handler, layer);
}
else {
// マーカー
var handler = drawControl._toolbars.draw._modes.marker.handler;
var layer = new L.Marker(latlng, handler.options);
layer.feature = feature;
L.Draw.Feature.prototype._fireCreatedEvent.call(handler, layer);
}
}
}
}
}
console.log("Imported file: " + e.target.files[0].name);
reader.readAsText(e.target.files[0]);
// input type fileで同じファイルで2回目以降onChangeが発火しない問題への対応(IE10だと下記ではダメらしい)
$('input[type=file]').val('');
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment