県境なしの白地図を作ります。県境を削除するには市区町村の境界線を消したのと同じ様な手順で作業をします。そして、現在のデータ量ではIllustratorでの表示には重すぎるのでデータを軽く扱いやすいサイズまで落とします。
プログラムは次の通りです。前回のものに県境を消す処理を追加しています。そしてXMLで座標データを保存してSVGを作成します。XMLフォルダにある各県の輪郭データを読み取ります。そしてjapanPrefNoBound.svgというSVGファイルを作成します。計算結果はjapanPrefNoBound.xmlに保存します。このスクリプトは実行にかなり時間がかかるので次回の描画からはこのXMLファイルを読み取って描画する事にします。
つまりここで作成したSVGは確認用です。
plotXML2JapanPrefNoBound.php
<?php //XMLフォルダに保存した各県のデータを読み取りSVGを描画 //県境を削除する。 ini_set('memory_limit', '2540M'); mb_language("Japanese");//文字コードの設定 mb_internal_encoding("UTF-8"); //ファイルリスト $xmlFile[1] ="./XML/hokkaido.xml"; $xmlFile[2] ="./XML/aomori.xml"; $xmlFile[3] ="./XML/iwate.xml"; $xmlFile[4] ="./XML/miyagi.xml"; $xmlFile[5] ="./XML/akita.xml"; $xmlFile[6] ="./XML/yamagata.xml"; $xmlFile[7] ="./XML/fukushima.xml"; $xmlFile[8] ="./XML/ibaraki.xml"; $xmlFile[9] ="./XML/tochigi.xml"; $xmlFile[10] ="./XML/gunnma.xml"; $xmlFile[11] ="./XML/saitama.xml"; $xmlFile[12] ="./XML/chiba.xml"; $xmlFile[13] ="./XML/tokyo.xml"; $xmlFile[14] ="./XML/kanagawa.xml"; $xmlFile[15] ="./XML/niigata.xml"; $xmlFile[16] ="./XML/toyama.xml"; $xmlFile[17] ="./XML/isihikawa.xml"; $xmlFile[18] ="./XML/fukui.xml"; $xmlFile[19] ="./XML/yamanashi.xml"; $xmlFile[20] ="./XML/nagano.xml"; $xmlFile[21] ="./XML/gifu.xml"; $xmlFile[22] ="./XML/sizuoka.xml"; $xmlFile[23] ="./XML/aiti.xml"; $xmlFile[24] ="./XML/mie.xml"; $xmlFile[25] ="./XML/shiga.xml"; $xmlFile[26] ="./XML/kyoto.xml"; $xmlFile[27] ="./XML/osaka.xml"; $xmlFile[28] ="./XML/hyougo.xml"; $xmlFile[29] ="./XML/nara.xml"; $xmlFile[30] ="./XML/wakayama.xml"; $xmlFile[31] ="./XML/tottori.xml"; $xmlFile[32] ="./XML/shimane.xml"; $xmlFile[33] ="./XML/okayama.xml"; $xmlFile[34] ="./XML/hiroshima.xml"; $xmlFile[35] ="./XML/yamaguchi.xml"; $xmlFile[36] ="./XML/tokushima.xml"; $xmlFile[37] ="./XML/kagawa.xml"; $xmlFile[38] ="./XML/ehime.xml"; $xmlFile[39] ="./XML/kouchi.xml"; $xmlFile[40] ="./XML/fukuoka.xml"; $xmlFile[41] ="./XML/saga.xml"; $xmlFile[42] ="./XML/nagasaki.xml"; $xmlFile[43] ="./XML/kumamoto.xml"; $xmlFile[44] ="./XML/ooita.xml"; $xmlFile[45] ="./XML/miyazaki.xml"; $xmlFile[46] ="./XML/kagoshima.xml"; $xmlFile[47] ="./XML/okinawa.xml"; $saveSVG = "./japanPrefNoBound.svg"; $saveXML = "./japanPrefNoBound.xml"; readXMLmakeOutline($xmlFile,$saveXML,$saveSVG); function readXMLmakeOutline($xmlFile,$saveXML,$saveSVG){ $startKenNo = 1; $endKenNo = 47; for ($ken=$startKenNo; $ken<$endKenNo+1; $ken++){ //XMLの読み込み $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->load($xmlFile[$ken]); $root = $dom->getElementsByTagName("*")->item(0);//最初の要素の中身を取り出す //読み込んだxmlの中身を$coordinateStringに展開する。 $j = 0; foreach( $root->getElementsByTagName("gml") as $itemGML ){//これでgmlの中身を$itemに取れる。 $dataArray = explode("\n",$itemGML->nodeValue);//改行コードで配列にわける $i=0; foreach ($dataArray as $item) {//各要素ごとに繰り返す $item = trim($item); if($item != ""){//空白文字列の時には処理をしない。 $coordinate[$ken][$j][$i]=$item; $i++; }//if }//foreach $j++; }//foreach( $root-> }//for ($ken=$startKenNo; $ken<$endKenNo+1; $ken++) XMLの読み込み完了 //各県のデータで重なりの部分を調べる。 for ($ken1=$startKenNo ;$ken1<$endKenNo+1 ; ++$ken1){ for ($ken2 = $ken1+1 ; $ken2<$endKenNo+1 ; ++$ken2){ //かさなるかどうかをarray_intersectを使いチェック //array_intersectが空なら重ならない。 //$coordinate[$ken1][$fig]の配列を一次元の配列に戻す。 $checkArray1 = array(); $checkArray2 = array(); $i=0; foreach ($coordinate[$ken1] as $fig) { foreach ($fig as $item) { $checkArray1[$i] = $item; $i++; } } $i=0; foreach ($coordinate[$ken2] as $fig) { foreach ($fig as $item) { $checkArray2[$i] = $item; $i++; } } $checkArray = array(); $checkArray = array_intersect($checkArray1,$checkArray2);//重なった値を返す //$checkArray = array_diff($checkArray1,$checkArray2); //var_dump($checkArray); if (count($checkArray) === 0){ //重なりが無い場合 $figCount1 = count($coordinate[$ken1]); $figCount2 = count($coordinate[$ken2]); //print("kasanari nashi figCount: " . $figCount2 . "/" . $figCount1 . "\n"); } else { //重なりがある場合 $figCount1 = count($coordinate[$ken1]); $figCount2 = count($coordinate[$ken2]); for ($fig1 = 0 ; $fig1 < $figCount1 ; $fig1++){ for ($fig2 = 0 ; $fig2 < $figCount2 ; $fig2++){ //ken1のfig1とken2のfig2に同一の点があるかチェックする。 $figcheckArray = array(); $figcheckArray = array_intersect($coordinate[$ken1][$fig1],$coordinate[$ken2][$fig2]);//重なった値を返す if (count($figcheckArray) === 0){ //fig同士の重なりが無い print("ken: (" . $ken1 . "/" . $endKenNo . ") (" . $ken2 . "/" . $endKenNo . ") : fig: (" . $fig1 . "/" . $figCount1 . ")\r"); } else { //fig同士の重なりがあるので次の処理へ print("ken: (" . $ken1 . "/" . $endKenNo . ") (" . $ken2 . "/" . $endKenNo . ") : fig: (" . $fig1 . "/" . $figCount1 . ")\r"); //$coordinate[$ken1][$fig1],$coordinate[$ken2][$fig2]を比較する。同一のデータがあれば削除する。ただし削除しないデータの順番は変えない。 $doublePoint = array();//重なっている座標を入れる配列。 $doublePoint = array_intersect($coordinate[$ken1][$fig1],$coordinate[$ken2][$fig2]);//重なった値を返す $doublePoint = array_values($doublePoint);//キーをつめる //$coordinate[$ken1][$fig1]と$doublePointで共通する値を削除する。 $coordinate[$ken1][$fig1] = array_diff($coordinate[$ken1][$fig1],$doublePoint);//配列の差を求める。$coordinate[$ken1][$fig1]から共通の項目を引く //ken2について $coordinate[$ken2][$fig2] = array_diff($coordinate[$ken2][$fig2],$doublePoint); $coordinate[$ken1][$fig1] = array_values($coordinate[$ken1][$fig1]);//キーをつめる $coordinate[$ken2][$fig2] = array_values($coordinate[$ken2][$fig2]);//キーをつめる }//if (count($figcheckArray) === 0 }//foreach($coordinate[$ken2] as $fig2 }//foreach($coordinate[$ken1] as $fig1 }//if count($checkArray) === 0 }//for ($ken2 }//for ($ken1 //ここまでの処理で都道府県の境界線を削除して海岸線だけのデータになっている。 //削除したデータは""になってる。これを削除してインデックスをつめる for ($ken=$startKenNo ;$ken<$endKenNo+1 ; ++$ken){ $figCount = count($coordinate[$ken]); for ($fig = 0 ; $fig < $figCount ; ++$fig){ $pointCount = count($coordinate[$ken][$fig]); for ($point = 0;$point < $pointCount; ++$point){ if ($coordinate[$ken][$fig][$point] === ""){ unset($coordinate[$ken][$fig][$point]); }//if }//for $point1 $coordinate[$ken][$fig] = array_values($coordinate[$ken][$fig]); }//for $fig1 }//for $ken1 //------------------数が少ない図形の配列は削除する。 for ($ken=$startKenNo ;$ken<$endKenNo+1 ; ++$ken){ $imax = count($coordinate[$ken]); //print("fig no.max origin=" . $imax ."\n"); for ($i=0 ;$i<$imax ; ++$i){ $jmax = count($coordinate[$ken][$i]); //print("jmax=" . $jmax ."\n"); if ($jmax < 20) {//要素が指定数以下の図形は削除 unset($coordinate[$ken][$i]);//削除する。 } } $coordinate[$ken] = array_values($coordinate[$ken]);//indexを詰める $imax = count($coordinate[$ken]); //print("fig no.max =" . $imax ."\n"); }//for $ken //======================距離を調べて点と点が離れている時には別の配列データにする。 for ($ken=$startKenNo ;$ken<$endKenNo+1 ; ++$ken){ $m = 0; $figMax = count($coordinate[$ken]); for ($i=0 ;$i<$figMax ; $i++){ $jmax = count($coordinate[$ken][$i]); $n = 0;//新しい配列に入れるためにnを0にする。 for ($j=0 ;$j<$jmax-1 ; $j++){ $latilongArray = explode(" ",$coordinate[$ken][$i][$j]);//空白区切りでデータを取り出す $latitude1String = $latilongArray[0];//各要素を取り出す $longitude1String = $latilongArray[1]; $latilongArray = explode(" ",$coordinate[$ken][$i][$j+1]);//空白区切りでデータを取り出す $latitude2String = $latilongArray[0];//各要素を取り出す $longitude2String = $latilongArray[1]; $x1 = floatval($latitude1String);//文字列のデータをfloatに変換する。 $y1 = floatval($longitude1String); $x2 = floatval($latitude2String); $y2 = floatval($longitude2String); $dx = $x2 - $x1; $dy = $y2 - $y1; $s = $dx * $dx + $dy * $dy;//$jと$j+1の距離の二乗 この値で判定する。 $temp[$ken][$m][$n] = $coordinate[$ken][$i][$j];//$jのデータを配列に入れる。tempは作業用の仮の配列 $n++;//次のためにnをひとつ進める。 if (($s > 1.0E-4) || ($n > 30000)){//$j+1が遠くにあるときの処理 もしくは一つの図形が大きくなりすぎた時に分割する。 $m++;//新規の図形 新しい配列に入れる。 $n = 0;//新しい配列の先頭から //$temp[$m][$n] = $coordinate[$i][$j+1]; } }// for $j $m++;//元の配列が一つ終わったら次の配列にする。 }// for $i $temp[$ken] = array_values($temp[$ken]);//キーをつめる } $coordinate = $temp;//元の配列に戻す。 // for ($ken=$startKenNo ;$ken<$endKenNo+1 ; ++$ken){ $figMax = count($coordinate[$ken]); for ($fig1=0 ;$fig1<$figMax ; $fig1++){ $dataCount = count($coordinate[$ken][$fig1]);//その行政区域の座標データ数を調べる if ($dataCount < 10){ unset($coordinate[$ken][$fig1]);//座標データ数がある値以下だったら削除 } } $coordinate[$ken] = array_values($coordinate[$ken]);//キーをつめる $deleteCount[$ken] = count($coordinate[$ken]); print("deleted($deleteCount/$figMax)\n"); } //===================================XMLへの保存 $contents='<?xml version="1.0" encoding="UTF-8"?>'; $contents .="\n"; $contents .='<ksj>' . "\n"; //境界線データの書き出し polylineでデータで書き出す。 for ($ken=$startKenNo ;$ken<$endKenNo+1 ; ++$ken){ $imax = count($coordinate[$ken]); for ($i=0 ;$i<$imax ; $i++){ $contents .='<gml>'. "\n";//図形の書き出し $jmax = count($coordinate[$ken][$i]); for ($j=0 ;$j<$jmax ; $j++){ $contents .= $coordinate[$ken][$i][$j] . "\n"; } $contents .= '</gml>' . "\n"; } } $contents .='</ksj>' . "\n"; file_put_contents($saveXML, $contents); //=========================SVGで書き出す //スクリーンサイズ $screenWidth = 4000; $screenHeight = 4000; //日本地図を描くための範囲を決める。 $north_max = 46.0;//最北端:北緯45度33分28秒 東経148度45分14秒 46.0 $south_max = 20.0;//最南端:北緯20度25分30.6585秒 東経136度4分11.1766秒 20.0 $east_max = 154.0;//最東端:北緯24度17分12秒 東経153度58分50秒 154.0 $west_max = 122.0;//最西端:北緯24度26分58秒 東経122度56分01秒 122.0 $y_max = log(tan(pi()/4.0 + deg2rad($north_max)/2.0)); $y_min = log(tan(pi()/4.0 + deg2rad($south_max)/2.0)); $x_max = deg2rad($east_max);//横方向 $x_min= deg2rad($west_max); $cLo = ($x_max - $x_min)/2 + $x_min; $dx = $x_max - $x_min; $dy = $y_max - $y_min; $mX = $screenWidth/$dx; $mY = $screenHeight/$dy; if ($mX < $mY){ $multiple = $mX; } else { $multiple = $mY; } //y軸の描画用の定数 $yOffset = $y_max; print("multiple=" . $multiple ."\n"); //==========================SVGファイルを書き出す $contents='<?xml version="1.0" standalone="no"?>'; $contents .="\n"; $contents .='<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'; $contents .="\n"; $contents .='<svg width="40cm" height="40cm" viewBox="0 0 4000 4000" xmlns="http://www.w3.org/2000/svg" version="1.1">'; $contents .="\n"; $contents .='<desc>都道府県の境界線データ</desc>'; $contents .="\n"; $contents .='<polyline fill="none" stroke="black" stroke-width="1" points="0,0 4000,0 4000,4000,0,4000" />';//図形の書き出し //境界線データの書き出し polylineでデータで書き出す。 for ($ken=$startKenNo; $ken<$endKenNo+1; $ken++){ $imax = count($coordinate[$ken]); print("imax=$imax\n"); for ($i=0 ;$i<$imax ; $i++){ $jmax = count($coordinate[$ken][$i]); //print("jmax=$jmax\n"); $svgData = ""; for ($j= 0;$j<$jmax ; $j++){ //メルカトル図法による計算。 $latilongArray = explode(" ",$coordinate[$ken][$i][$j]);//空白で区切る $latitude1String = $latilongArray[0];//各要素を取り出す $longitude1String = $latilongArray[1]; $radian = deg2rad(floatval($latitude1String)); $x = deg2rad(floatval($longitude1String)) - $cLo;//横方向 $y = log(tan(pi()/4.0 + $radian/2.0));//縦方向 $x = $multiple * $x + $screenWidth/2; $y = $multiple * (1.0 - $y) - ($multiple * (1.0-$yOffset)); $svgData .= $x . "," . $y . " "; } $contents .='<polyline fill="none" stroke="blue" stroke-width="1" points="';//図形の書き出し $contents .= $svgData . '" />' . "\n"; } } $contents .="</svg>"; file_put_contents($saveSVG, $contents); }// end of function
このスクリプトで作成したSVGファイルは227MBもあり、描画は向きではありません。細かい部分も入っているのでデータ数を減らす工夫が必要になります。下図はChromeで表示したSVGのスクrーんショットです。
県境が入っているデータも同様にデータを減らす必要があります。できれば県で閉じたパスにしたいのですが、隣の県との県境を合わせた形で座標点を間引く必要があるので、その処理をどうやろうか?ちょっと考えます。
とりあえず日本の海岸線のデータだけであれば今回作成したXMLデータを元にすれば良いのでそれを元にデータを減らします。点と点の距離を計算してある値以下なら座標データを削除します。
//データの削除を行う。 $imax = count($coordinate); for ($i=0 ;$i<$imax-1 ; $i++){ $j = 0; $latilongArray = explode(" ",$coordinate[$i][$j]);//空白で区切る $ox = floatval($latilongArray[0]);//各要素を取り出す $oy = floatval($latilongArray[1]); $jmax = count($coordinate[$i]); for ($j= 1;$j<$jmax-1 ; $j++){//最後の点は残すので$jmax-1でループをまわす $latilongArray = explode(" ",$coordinate[$i][$j]);//空白で区切る $x = floatval($latilongArray[0]);//各要素を取り出す $y = floatval($latilongArray[1]); $dx = $ox - $x; $dy = $oy - $y; $s = $dx * $dx + $dy * $dy;//$jと$j+1の距離の二乗 この値で判定する。 if ($s > 1.0E-6){ $ox = $x; $oy = $y; } else { unset($coordinate[$i][$j]);//描画しない時 データを削除 }//if }//for j $coordinate[$i] = array_values($coordinate[$i]);//indexを詰める }//for i $coordinate = array_values($coordinate);//indexを詰める
その次に一定数以下のデータは削除します。
//データの削除を行う。 $imax = count($coordinate); for ($i=0 ;$i<$imax-1 ; $i++){ $jmax = count($coordinate[$i]); if ($jmax < 20){ unset($coordinate[$i]);//削除する。 } } $coordinate = array_values($coordinate);//indexを詰める
プログラム全文は次の通りです。
plotJapanOutline.php
<?php ini_set('memory_limit', '2048M'); //makeXMLfromGML2.phpで作ったxmlデータからSVGを作成するためのphp mb_language("Japanese");//文字コードの設定 mb_internal_encoding("UTF-8"); //ファイル $xmlFile ="./japanPrefNoBound.xml"; $svgFile = "./japanPrefNoBound_light.svg"; xml2svg($xmlFile,$svgFile); function xml2svg($xmlFile,$svgFile){ $dom = new DOMDocument('1.0', 'UTF-8'); $dom->preserveWhiteSpace = false; $dom->formatOutput = true; $dom->load($xmlFile); $root = $dom->getElementsByTagName("*")->item(0);//最初の要素の中身を取り出す //読み込んだxmlの中身を$coordinateStringに展開する。 $j = 0; foreach( $root->getElementsByTagName("gml") as $itemGML ){//これでgmlの中身を$itemに取れる。 $dataArray = explode("\n",$itemGML->nodeValue);//改行コードで配列にわける $i=0; foreach ($dataArray as $item) {//各要素ごとに繰り返す $item = trim($item); if($item != ""){//空白文字列の時には処理をしない。 $coordinate[$j][$i]=$item; $i++; }//if }//foreach $j++; }//foreach( $root-> //データの削除を行う。 $imax = count($coordinate); for ($i=0 ;$i<$imax-1 ; $i++){ $j = 0; $latilongArray = explode(" ",$coordinate[$i][$j]);//空白で区切る $ox = floatval($latilongArray[0]);//各要素を取り出す $oy = floatval($latilongArray[1]); $jmax = count($coordinate[$i]); for ($j= 1;$j<$jmax-1 ; $j++){//最後の点は残すので$jmax-1でループをまわす $latilongArray = explode(" ",$coordinate[$i][$j]);//空白で区切る $x = floatval($latilongArray[0]);//各要素を取り出す $y = floatval($latilongArray[1]); $dx = $ox - $x; $dy = $oy - $y; $s = $dx * $dx + $dy * $dy;//$jと$j+1の距離の二乗 この値で判定する。 if ($s > 1.0E-6){ $ox = $x; $oy = $y; } else { unset($coordinate[$i][$j]);//描画しない時 データを削除 }//if }//for j $coordinate[$i] = array_values($coordinate[$i]);//indexを詰める }//for i $coordinate = array_values($coordinate);//indexを詰める //データの削除を行う。 $imax = count($coordinate); for ($i=0 ;$i<$imax-1 ; $i++){ $jmax = count($coordinate[$i]); if ($jmax < 20){ unset($coordinate[$i]);//削除する。 } } $coordinate = array_values($coordinate);//indexを詰める //=========================SVGで書き出す //スクリーンサイズ $screenWidth = 4000; $screenHeight = 4000; //日本地図を描くための範囲を決める。 $north_max = 46.0;//最北端:北緯45度33分28秒 東経148度45分14秒 46.0 $south_max = 20.0;//最南端:北緯20度25分30.6585秒 東経136度4分11.1766秒 20.0 $east_max = 154.0;//最東端:北緯24度17分12秒 東経153度58分50秒 154.0 $west_max = 122.0;//最西端:北緯24度26分58秒 東経122度56分01秒 122.0 $y_max = log(tan(pi()/4.0 + deg2rad($north_max)/2.0)); $y_min = log(tan(pi()/4.0 + deg2rad($south_max)/2.0)); $x_max = deg2rad($east_max);//横方向 $x_min= deg2rad($west_max); $cLo = ($x_max - $x_min)/2 + $x_min; $dx = $x_max - $x_min; $dy = $y_max - $y_min; $mX = $screenWidth/$dx; $mY = $screenHeight/$dy; if ($mX < $mY){ $multiple = $mX; } else { $multiple = $mY; } //y軸の描画用の定数 $yOffset = $y_max; //==========================SVGファイルを書き出す $contents='<?xml version="1.0" standalone="no"?>'; $contents .="\n"; $contents .='<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'; $contents .="\n"; $contents .='<svg width="40cm" height="40cm" viewBox="0 0 4000 4000" xmlns="http://www.w3.org/2000/svg" version="1.1">'; $contents .="\n"; $contents .='<desc>都道府県の境界線データ</desc>'; $contents .="\n"; $contents .='<polyline fill="none" stroke="black" stroke-width="1" points="0,0 4000,0 4000,4000,0,4000" />';//図形の書き出し //境界線データの書き出し polylineでデータで書き出す。 $imax = count($coordinate); print("imax=$imax\n"); for ($i=0 ;$i<$imax ; $i++){ $jmax = count($coordinate[$i]); //print("jmax=$jmax\n"); $svgData = ""; for ($j= 0;$j<$jmax ; $j++){ //メルカトル図法による計算。 $latilongArray = explode(" ",$coordinate[$i][$j]);//空白で区切る $latitude1String = $latilongArray[0];//各要素を取り出す $longitude1String = $latilongArray[1]; $radian = deg2rad(floatval($latitude1String)); $x = deg2rad(floatval($longitude1String)) - $cLo;//横方向 $y = log(tan(pi()/4.0 + $radian/2.0));//縦方向 $x = $multiple * $x + $screenWidth/2; $y = $multiple * (1.0 - $y) - ($multiple * (1.0-$yOffset)); $svgData .= $x . "," . $y . " "; } $contents .='<polyline fill="none" stroke="blue" stroke-width="1" points="';//図形の書き出し $contents .= $svgData . '" />' . "\n"; } $contents .="</svg>"; file_put_contents($svgFile, $contents); }
これで描画した図です。Illustratorのプレビュー表示のスクリーンショットです。SVGファイルは8.2MBになりました。
小さな島を削除するともっとデータ軽くできそう。