国土数値地理情報から白地図を作る。6

今までの手順で作成した白地図は県の輪郭線が途切れていました。そこで輪郭線が途切れない様に処理を変更しました。市区町村の重なりを求め削除し、そこから都道府県の輪郭線を作るという流れは同じですが、市区町村の重なりを作った時に内側に一つ座標を短くしています。それにより都道府県の輪郭が切れずに求まります。

プログラムの全文は次の通りです。下に部分ごとの解説を書いてあります。いままでのプログラムはfunctionを使わずに書いてて見通しがわるかったので機能ごとにfunctionにまとめてあります。

makePrefBoundNoJoint.php

<?php
//国土数値情報行政区域から県境だけのデータを作る。
//各県の境界線と海岸線を切れ目なしで作る。
//市区町村の重なりを調べる。端っこを1つオフセットする。最後に引き算で輪郭を求める。



ini_set('memory_limit', '1280M');

mb_language("Japanese");//文字コードの設定
mb_internal_encoding("UTF-8");

$loadXML[1] = "./国土数値情報行政区域/N03-170101_01_GML/N03-17_01_170101.xml"; $saveFile[1] ="hokkaido";
$loadXML[2] = "./国土数値情報行政区域/N03-170101_02_GML/N03-17_02_170101.xml"; $saveFile[2] ="aomori";
$loadXML[3] = "./国土数値情報行政区域/N03-170101_03_GML/N03-17_03_170101.xml"; $saveFile[3] ="iwate";
$loadXML[4] = "./国土数値情報行政区域/N03-170101_04_GML/N03-17_04_170101.xml"; $saveFile[4] ="miyagi";
$loadXML[5] = "./国土数値情報行政区域/N03-170101_05_GML/N03-17_05_170101.xml"; $saveFile[5] ="akita";
$loadXML[6] = "./国土数値情報行政区域/N03-170101_06_GML/N03-17_06_170101.xml"; $saveFile[6] ="yamagata";
$loadXML[7] = "./国土数値情報行政区域/N03-170101_07_GML/N03-17_07_170101.xml"; $saveFile[7] ="fukushima";
$loadXML[8] = "./国土数値情報行政区域/N03-170101_08_GML/N03-17_08_170101.xml"; $saveFile[8] ="ibaraki";
$loadXML[9] = "./国土数値情報行政区域/N03-170101_09_GML/N03-17_09_170101.xml"; $saveFile[9] ="tochigi";
$loadXML[10] = "./国土数値情報行政区域/N03-170101_10_GML/N03-17_10_170101.xml"; $saveFile[10] ="gunnma";
$loadXML[11] = "./国土数値情報行政区域/N03-170101_11_GML/N03-17_11_170101.xml"; $saveFile[11] ="saitama";
$loadXML[12] = "./国土数値情報行政区域/N03-170101_12_GML/N03-17_12_170101.xml"; $saveFile[12] ="chiba";
$loadXML[13] = "./国土数値情報行政区域/N03-170101_13_GML/N03-17_13_170101.xml"; $saveFile[13] ="tokyo";
$loadXML[14] = "./国土数値情報行政区域/N03-170101_14_GML/N03-17_14_170101.xml"; $saveFile[14] ="kanagawa";
$loadXML[15] = "./国土数値情報行政区域/N03-170101_15_GML/N03-17_15_170101.xml"; $saveFile[15] ="niigata";
$loadXML[16] = "./国土数値情報行政区域/N03-170101_16_GML/N03-17_16_170101.xml"; $saveFile[16] ="toyama";
$loadXML[17] = "./国土数値情報行政区域/N03-170101_17_GML/N03-17_17_170101.xml"; $saveFile[17] ="isihikawa";
$loadXML[18] = "./国土数値情報行政区域/N03-170101_18_GML/N03-17_18_170101.xml"; $saveFile[18] ="fukui";
$loadXML[19] = "./国土数値情報行政区域/N03-170101_19_GML/N03-17_19_170101.xml"; $saveFile[19] ="yamanashi";
$loadXML[20] = "./国土数値情報行政区域/N03-170101_20_GML/N03-17_20_170101.xml"; $saveFile[20] ="nagano";
$loadXML[21] = "./国土数値情報行政区域/N03-170101_21_GML/N03-17_21_170101.xml"; $saveFile[21] ="gifu";
$loadXML[22] = "./国土数値情報行政区域/N03-170101_22_GML/N03-17_22_170101.xml"; $saveFile[22] ="sizuoka";
$loadXML[23] = "./国土数値情報行政区域/N03-170101_23_GML/N03-17_23_170101.xml"; $saveFile[23] ="aiti";
$loadXML[24] = "./国土数値情報行政区域/N03-170101_24_GML/N03-17_24_170101.xml"; $saveFile[24] ="mie";
$loadXML[25] = "./国土数値情報行政区域/N03-170101_25_GML/N03-17_25_170101.xml"; $saveFile[25] ="shiga";
$loadXML[26] = "./国土数値情報行政区域/N03-170101_26_GML/N03-17_26_170101.xml"; $saveFile[26] ="kyoto";
$loadXML[27] = "./国土数値情報行政区域/N03-170101_27_GML/N03-17_27_170101.xml"; $saveFile[27] ="osaka";
$loadXML[28] = "./国土数値情報行政区域/N03-170101_28_GML/N03-17_28_170101.xml"; $saveFile[28] ="hyougo";
$loadXML[29] = "./国土数値情報行政区域/N03-170101_29_GML/N03-17_29_170101.xml"; $saveFile[29] ="nara";
$loadXML[30] = "./国土数値情報行政区域/N03-170101_30_GML/N03-17_30_170101.xml"; $saveFile[30] ="wakayama";
$loadXML[31] = "./国土数値情報行政区域/N03-170101_31_GML/N03-17_31_170101.xml"; $saveFile[31] ="tottori";
$loadXML[32] = "./国土数値情報行政区域/N03-170101_32_GML/N03-17_32_170101.xml"; $saveFile[32] ="shimane";
$loadXML[33] = "./国土数値情報行政区域/N03-170101_33_GML/N03-17_33_170101.xml"; $saveFile[33] ="okayama";
$loadXML[34] = "./国土数値情報行政区域/N03-170101_34_GML/N03-17_34_170101.xml"; $saveFile[34] ="hiroshima";
$loadXML[35] = "./国土数値情報行政区域/N03-170101_35_GML/N03-17_35_170101.xml"; $saveFile[35] ="yamaguchi";
$loadXML[36] = "./国土数値情報行政区域/N03-170101_36_GML/N03-17_36_170101.xml"; $saveFile[36] ="tokushima";
$loadXML[37] = "./国土数値情報行政区域/N03-170101_37_GML/N03-17_37_170101.xml"; $saveFile[37] ="kagawa";
$loadXML[38] = "./国土数値情報行政区域/N03-170101_38_GML/N03-17_38_170101.xml"; $saveFile[38] ="ehime";
$loadXML[39] = "./国土数値情報行政区域/N03-170101_39_GML/N03-17_39_170101.xml"; $saveFile[39] ="kouchi";
$loadXML[40] = "./国土数値情報行政区域/N03-170101_40_GML/N03-17_40_170101.xml"; $saveFile[40] ="fukuoka";
$loadXML[41] = "./国土数値情報行政区域/N03-170101_41_GML/N03-17_41_170101.xml"; $saveFile[41] ="saga";
$loadXML[42] = "./国土数値情報行政区域/N03-170101_42_GML/N03-17_42_170101.xml"; $saveFile[42] ="nagasaki";
$loadXML[43] = "./国土数値情報行政区域/N03-170101_43_GML/N03-17_43_170101.xml"; $saveFile[43] ="kumamoto";
$loadXML[44] = "./国土数値情報行政区域/N03-170101_44_GML/N03-17_44_170101.xml"; $saveFile[44] ="ooita";
$loadXML[45] = "./国土数値情報行政区域/N03-170101_45_GML/N03-17_45_170101.xml"; $saveFile[45] ="miyazaki";
$loadXML[46] = "./国土数値情報行政区域/N03-170101_46_GML/N03-17_46_170101.xml"; $saveFile[46] ="kagoshima";
$loadXML[47] = "./国土数値情報行政区域/N03-170101_47_GML/N03-17_47_170101.xml"; $saveFile[47] ="okinawa";


$startKenNo = 1;
$endKenNo = 47;

//県ごとに処理を繰り返す
for ($i=$startKenNo; $i<$endKenNo+1; $i++){
  $startTime = microtime(true);
  $coordinate = readXMLfromFile($loadXML[$i]);//XMLから読み込み

  $max = getMaxPointDistance($coordinate);//座標データで一番はなれているところの二乗を求める。
  $ave = getAvePointDistance($coordinate);//平均を求める

  $cityBorder = checkBorder($coordinate);//市区町村の境界線を求める。
  $cityBorder = separateBorder($cityBorder,$max);//線が飛んでいるところを別の図形にする。
  $cityBorder = arrayCleanup($cityBorder);//配列をきれいにする。

  $cityBorder = cityBoarderEdge($cityBorder);//ここで端の線を1つ短くする処理をする。両側を短くする。
  $cityBorder = arrayCleanup($cityBorder);//配列をきれいにする。

  $prefBorder = getPrefBorder($coordinate,$cityBorder);//すべてのデータcoordinateと市区町村の境界線から県境をもとめる。
  $prefBorder = separateBorder($prefBorder,$max);//線が飛んでいる部分を処理する。
  $prefBorder = arrayCleanup($prefBorder);//配列をきれいにする。
  $prefBorder = deleteShortLine($prefBorder);//点が3つ以下の部分を削除
  $prefBorder = deleteEndLine($prefBorder,$ave);//図形の端っこの点が離れている場合には削除する。

  $saveSVG = "./PrefSVG/" .$saveFile[$i] .".svg";
  $saveXML = "./PrefXML/" .$saveFile[$i] .".xml";

  drawSVG($saveSVG,$prefBorder);
  saveXMLdata($saveXML,$prefBorder);

  $endTime = microtime(true);
  $exeTime = $endTime - $startTime;
  print("    [$saveFile[$i]]exe Time=".$exeTime . "\n");
}


//=====================県ごとの座標と座標の距離をはかり最大値を返す。
function getMaxPointDistance($coordinate){
  $min = 10.00;
  $max = 0.0;
  $figMax = count($coordinate);
  for ($i=0 ;$i<$figMax ; $i++){
    $jmax = count($coordinate[$i]);

    $j = 0;
    $latilongArray = explode(" ",$coordinate[$i][$j]);//空白区切りでデータを取り出す
    $ox = floatval($latilongArray[0]);
    $oy = floatval($latilongArray[1]);

    for ($j=1 ;$j<$jmax ; $j++){
      $latilongArray = explode(" ",$coordinate[$i][$j]);//空白区切りでデータを取り出す
      $x = floatval($latilongArray[0]);
      $y = floatval($latilongArray[1]);

      $dx = $ox - $x;
      $dy = $oy - $y;
      $s = $dx * $dx  + $dy * $dy;//距離の二乗 この値で判定する。
      $ox = $x;
      $oy = $y;

      if ($min>$s){
        $min = $s;
      }
      if ($max<$s){
        $max = $s;
      }
    }// for $j
  }// for $i}

  return $max;
}



//=====================県ごとの座標と座標の距離をはかり平均値を返す。
function getAvePointDistance($coordinate){

  $total = 0.0;

  $averageArray = array();

  $figMax = count($coordinate);
  for ($i=0 ;$i<$figMax ; $i++){
    $count = 0;
    $total = 0;
    $jmax = count($coordinate[$i]);

    $j = 0;
    $latilongArray = explode(" ",$coordinate[$i][$j]);//空白区切りでデータを取り出す
    $ox = floatval($latilongArray[0]);
    $oy = floatval($latilongArray[1]);

    for ($j=1 ;$j<$jmax ; $j++){
      $latilongArray = explode(" ",$coordinate[$i][$j]);//空白区切りでデータを取り出す
      $x = floatval($latilongArray[0]);
      $y = floatval($latilongArray[1]);

      $dx = $ox - $x;
      $dy = $oy - $y;
      $s = $dx * $dx  + $dy * $dy;//距離の二乗 この値で判定する。
      $ox = $x;
      $oy = $y;

      $count++;
      $total = $total + $s;
    }// for $j
    $averageArray[$i] = $total / $count;
  }// for $i}

  $total = array_sum($averageArray);
  $ave = $total / count($averageArray);
  return $ave;
}


//======================市区町村の橋を処理する。
function cityBoarderEdge($inputArray){
  $iMax = count($inputArray);
  for ($i=0 ;$i<$iMax ; $i++){
    $jMax = count($inputArray[$i]);
    if($jMax !=0){
      $k = 0;
      unset($inputArray[$i][$k]);
      $inputArray[$i] = array_values($inputArray[$i]);//削除した分をつめる

      $k = count($inputArray[$i])-1;
      unset($inputArray[$i][$k]);

      $inputArray[$i] = array_values($inputArray[$i]);
    }//if
  }
  return $inputArray;
}




//=======================市区町村の境界線を求める。
function checkBorder($coordinate){
  //かさなっている部分、市区町村の境界線を$innerBorderに入れる。
  $figMax = count($coordinate);
  $c = 0;
  for ($fig1=0; $fig1<$figMax; $fig1++){
    echo("   step1 (" . $fig1 . "/" . $figMax . ")   \r");
    for ($fig2=$fig1+1; $fig2<$figMax; $fig2++){
      $overlap = array_intersect($coordinate[$fig1],$coordinate[$fig2]);//重なった値を返す
      if (count($overlap) !== 0){
        $innerBorder[$c] = array_values($overlap);
        $c++;
      }//if
    }//fig2
  }//fig1

  //値が一つしかない点のデータの時には削除する。
  $iMax = count($innerBorder);
  for ($i=0; $i<$iMax; $i++){
    if (count($innerBorder[$i])==1){
      unset($innerBorder[$i]);
    }
  }
  $innerBorder = array_values($innerBorder);

  return $innerBorder;
}




//=========================値が3つ以下の点のデータの時には削除する。
function deleteShortLine($innerBorder){
  $iMax = count($innerBorder);
  for ($i=0; $i<$iMax; $i++){
    echo("   step3 (" . $i . "/" . $iMax . ")   \r");
    if (isset($innerBorder[$i])){
      $a = count($innerBorder[$i])-1;
      if ($a <= 5){//図形のポイントが少ない時に他の図形と端点が重ならない時に削除
        $X = $innerBorder[$i][0];
        $Y = $innerBorder[$i][$a];
        for ($j=0; $j<$iMax; $j++){//他の図形と比較
          if (isset($innerBorder[$j])){
            $b = count($innerBorder[$j])-1;//最後の点の番号
            if ($b > 5 ){
              $pointA = $innerBorder[$j][0];//重なりがあるかチェック
              $pointB = $innerBorder[$j][$b];
              if($pointA === $X || $pointA === $Y ||//
                 $pointB === $X || $pointB === $Y ){
                   //点がどこかと重なっている。
              } else {
                unset($innerBorder[$i]);
              }//if
            }//if
          }
        }//for j
      }//if
    }//isset
  }//for i
  $innerBorder = array_values($innerBorder);

  return $innerBorder;
}




//=======================市区町村の境界線から県境を求める。
function getPrefBorder($coordinate,$cityBorder){
  $prefBorder = $coordinate;//データを$prefBorderにいれてこちらで処理する。

  $iMax = count($prefBorder);
  $jMax = count($cityBorder);
  //print("iMax=$iMax jMax=$jMax\n");
  for ($i=0; $i<$iMax; $i++){
    echo("    step2  (" . $i . "/" . $iMax . ")   \r");
    for ($j=0 ;$j<$jMax; $j++){
      $checkArray = array_intersect($prefBorder[$i],$cityBorder[$j]);//重なった値を返す
      if (count($checkArray)!== 0){//重なった時だけ引き算をする。
        $prefBorder[$i] = array_values(array_diff($prefBorder[$i],$cityBorder[$j]));
      }
    }//for $j
  }//for $i

  $prefBorder = array_values($prefBorder);

  return $prefBorder;
}




//===================線が飛んでいる場合には2つの図形にわける。
function separateBorder($prefBorder,$max){
  $iMax = count($prefBorder);
  $m = 0;
  for ($i=0 ;$i<$iMax ; $i++){
   $jMax = count($prefBorder[$i]);
   if($jMax !=0){
     $n = 0;//新しい配列に入れるためにnを0にする。

     $j=0;
     $coadingData = explode(" ",$prefBorder[$i][$j]);//空白区切りでデータを取り出す
     $ox = floatval($coadingData[0]);//各要素を取り出す 文字列のデータをfloatに変換する。
     $oy = floatval($coadingData[1]);
     $temp[$m][$n] = $prefBorder[$i][$j];

     for ($j=1 ;$j<$jMax ; $j++){
       $n++;//次のためにnをひとつ進める。
       $coadingData = explode(" ",$prefBorder[$i][$j]);//空白区切りでデータを取り出す
       $x = floatval($coadingData[0]);//各要素を取り出す 文字列のデータをfloatに変換する。
       $y = floatval($coadingData[1]);

       $dx = $x - $ox;
       $dy = $y - $oy;
       $s = $dx * $dx  + $dy * $dy;//$jと$j+1の距離の二乗 この値で判定する。

       if ($s > $max){//遠くにあるときの処理,最初のデータの中で最大の距離が$max。それよりも遠いものをはなれていると判断する。
         $m++;//新規の図形 新しい配列に入れる。
         $n = 0;//新しい配列の先頭から
       }

       $temp[$m][$n] = $prefBorder[$i][$j];//$jのデータを配列に入れる。tempは作業用の仮の配列

       $ox = $x;
       $oy = $y;
     }
     $m++;//元の配列が一つ終わったら次の配列にする。
   }// for $j

 }// for $i

 return $temp;
}


//=====================両端が離れている場合は削除
function deleteEndLine($coordinate,$max){
  $iMax = count($coordinate);
  for ($i=0; $i<$iMax; $i++){
    $a = count($coordinate[$i])-1;
    $p1 = $coordinate[$i][0];
    $p2 = $coordinate[$i][1];
    $p3 = $coordinate[$i][$a-1];
    $p4 = $coordinate[$i][$a];

    $coadingData = explode(" ",$p1);//空白区切りでデータを取り出す
    $ox = floatval($coadingData[0]);//各要素を取り出す 文字列のデータをfloatに変換する。
    $oy = floatval($coadingData[1]);

    $coadingData = explode(" ",$p2);//空白区切りでデータを取り出す
    $x = floatval($coadingData[0]);//各要素を取り出す 文字列のデータをfloatに変換する。
    $y = floatval($coadingData[1]);

    $dx = $x - $ox;
    $dy = $y - $oy;
    $s = $dx * $dx  + $dy * $dy;//$jと$j+1の距離の二乗 この値で判定する。
    if($s > $max ){
      unset($coordinate[$i][0]);
    }//if

    $coadingData = explode(" ",$p3);//空白区切りでデータを取り出す
    $ox = floatval($coadingData[0]);//各要素を取り出す 文字列のデータをfloatに変換する。
    $oy = floatval($coadingData[1]);

    $coadingData = explode(" ",$p4);//空白区切りでデータを取り出す
    $x = floatval($coadingData[0]);//各要素を取り出す 文字列のデータをfloatに変換する。
    $y = floatval($coadingData[1]);

    $dx = $x - $ox;
    $dy = $y - $oy;
    $s = $dx * $dx  + $dy * $dy;//$jと$j+1の距離の二乗 この値で判定する。
    if($s > $max ){
      unset($coordinate[$i][$a]);
    }//if

    $coordinate[$i] = array_values($coordinate[$i]);
  }//for i

  $coordinate = array_values($coordinate);

  return $coordinate;
}



//============================配列から空白やゼロの部分を取り除く
function arrayCleanup($coordinate){
  $figMax = count($coordinate);
  //print("in=$figMax\n");
  for ($i= 0;$i<$figMax ; $i++){
    $iMax = count($coordinate[$i]);
    if ($iMax != 0){
      for ($j = 0 ; $j<$iMax; $j++){
        if ($coordinate[$i][$j] == ""){
          unset($coordinate[$i][$j]);
          print("*");
        }// if
      }//for j
      $coordinate[$i] = array_values($coordinate[$i]);
    } else {
      unset($coordinate[$i]);
    }
  }//for i
  //print("\n");
  $coordinate = array_values($coordinate);
  $figMax2 = count($coordinate);
  //print("int=$figMax    out=$figMax2     \n");
  return $coordinate;
}


//=========================XMLで図形を書き出す
function saveXMLdata($saveXML,$coordinate){
  $contents='<?xml version="1.0" encoding="UTF-8"?>' . "\n";
  $contents .='<ksj>' . "\n";

  $figMax = count($coordinate);
  for ($fig=0; $fig<$figMax; $fig++){
    $jmax = count($coordinate[$fig]);
    $contents .='<obj>'. "\n";//県の書き出し
    for ($j=0 ;$j<$jmax ; $j++){
      $contents .= $coordinate[$fig][$j] . "\n";
    }//for j
    $contents .='</obj>'. "\n";
  }//for fig

  $contents .='</ksj>' . "\n";
  file_put_contents($saveXML, $contents);

}//function



//=====================配列データからSVGを書き出すfunction=========================
function drawSVG($saveSVG,$coordinate){
  //スクリーンサイズ
  $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"?>' . "\n";
  $contents .='<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' . "\n";
  $contents .='<svg width="40cm" height="40cm" viewBox="0 0 4000 4000" xmlns="http://www.w3.org/2000/svg" version="1.1">' . "\n";
  $contents .='<desc>都道府県の境界線データ</desc>' . "\n";
  $contents .='<polyline fill="none" stroke="black" stroke-width="1" points="0,0 4000,0 4000,4000,0,4000" />'. "\n";//図形の書き出し
  //境界線データの書き出し polylineでデータで書き出す。

    $figMax = count($coordinate);
    //print("figMax=$figMax\n");

  for ($fig= 0;$fig<$figMax ; $fig++){
    $iMax = count($coordinate[$fig]);
    if ($iMax != 0){
      $svgData = "";
      for ($i = 0 ; $i<$iMax; $i++){
        //メルカトル図法による計算。
        $latilongArray = explode(" ",$coordinate[$fig][$i]);//空白で区切る
        //print("strring=" . $coordinate[$i][$j] . "\n");
        $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 . " ";
      }// for i
      $contents .='<polyline fill="none" stroke="black" stroke-width="1" points="';//図形の書き出し
      $contents .= $svgData . '" />' . "\n";
    }
  }//for fig


  $contents .="</svg>";

  file_put_contents($saveSVG, $contents);
}




//==========================XMLをファイルから読み込むfunction======================
function readXMLfromFile($xmlFile){
  //print("XML file loading...\n");
  $dom = new DOMDocument('1.0', 'UTF-8');
  $dom->preserveWhiteSpace = false;
  $dom->formatOutput = true;
  $dom->load($xmlFile);

  $root = $dom->getElementsByTagName("*")->item(0);//最初の要素の中身を取り出す

  $coordinate = array();
  $i = 0;
  foreach( $root->getElementsByTagNameNS("*","Curve") as $itemGML ){//これでgml:Curveのところが取れるらしい。
    $dataArray = explode("\n",$itemGML->nodeValue);//改行コードで配列にわける

    $j=0;
    foreach ($dataArray as $item) {//各要素ごとに繰り返す
      $item = trim($item);
      if($item != ""){//空白文字列の時には処理をしない。
        $coordinate[$i][$j]=$item;
        $j++;
      }//if
    }//foreach
    $i++;
  }//foreach
  return $coordinate;
}

XMLはGISのサイトからダウンロードしたXMLを読み込むようにしてあります。ここらへんは国土数値情報から白地図を作る1をみてください。

メインの処理は次の通りです。forループで47都道府県を順番に処理しています。

for ($i=$startKenNo; $i<$endKenNo+1; $i++){
  $startTime = microtime(true);
  $coordinate = readXMLfromFile($loadXML[$i]);//XMLから読み込み

  $max = getMaxPointDistance($coordinate);//座標データで一番はなれているところの二乗を求める。
  $ave = getAvePointDistance($coordinate);//平均を求める

  $cityBorder = checkBorder($coordinate);//市区町村の境界線を求める。
  $cityBorder = separateBorder($cityBorder,$max);//線が飛んでいるところを別の図形にする。
  $cityBorder = arrayCleanup($cityBorder);//配列をきれいにする。

  $cityBorder = cityBoarderEdge($cityBorder);//ここで端の線を1つ短くする処理をする。両側を短くする。
  $cityBorder = arrayCleanup($cityBorder);//配列をきれいにする。

  $prefBorder = getPrefBorder($coordinate,$cityBorder);//すべてのデータcoordinateと市区町村の境界線から県境をもとめる。
  $prefBorder = separateBorder($prefBorder,$max);//線が飛んでいる部分を処理する。
  $prefBorder = arrayCleanup($prefBorder);//配列をきれいにする。
  $prefBorder = deleteShortLine($prefBorder);//点が3つ以下の部分を削除
  $prefBorder = deleteEndLine($prefBorder,$ave);//図形の端っこの点が離れている場合には削除する。

  $saveSVG = "./PrefSVG/" .$saveFile[$i] .".svg";
  $saveXML = "./PrefXML/" .$saveFile[$i] .".xml";

  drawSVG($saveSVG,$prefBorder);
  saveXMLdata($saveXML,$prefBorder);

  $endTime = microtime(true);
  $exeTime = $endTime - $startTime;
  print("    [$saveFile[$i]]exe Time=".$exeTime . "\n");
}

47都道府県のどの都道府県を処理するのかはstartとendを指定して処理することもできます。

$startKenNo = 1;
$endKenNo = 1;

全都道府県を処理する場合には1〜47になります。

$coordinate = readXMLfromFile($loadXML[$i]);

最初にXMLを読み込みます。読み込んだ結果は配列$coordinateに入ります。この配列は2次元配列です。最初の添え字で市区町村または島などの海岸線の番号。2番目の添え字で座標のデータが入っています。
読み込んだデータを描画すると次のようになります。下図は新潟県の場合です。図はIllustratorのアウトライン表示です。佐渡が消えてしまってますがIllustratorの図形の限界を超えてしまっているので描画されていません。データとしては存在します。Illusratorは一つのオブジェクトのポイントが3万を越えると表示ができなくなるようです。佐渡島の周囲にある岩などで佐渡形がかろうじてわかります。

データの読み込み後の最初の処理は、後の処理で使うために座標データと座標データの距離を求めておきます。この値は都道府県によってかなりバラツキがあるので各都道府県で求めて後の処理で使います。最大値と平均値を求めます。求めた値は$maxと$aveに入れます。

  $max = getMaxPointDistance($coordinate);//座標データで一番はなれているところの二乗を求める。
  $ave = getAvePointDistance($coordinate);//平均を求める

次にこの配列から市区町村の境界線を求める処理を行います。この処理が一番時間がかかります。

$cityBorder = checkBorder($coordinate);//市区町村の境界線を求める。

県の中の市区町村の境界線だけを求めます。計算結果は配列$cityBorderに入ります。functionの中の処理は全ての市区町村の境界線が重なるかどうかarray_intersectを使いチェックします。重なっていた場合に重なったデータを返す様にしています。この配列も2次元配列です。最初の添え字で図形の番号。この番号でひとつなぎの境界線のデータになります。2番目の添え字で座標のデータが入っています。新潟県の場合はcityBorderを描画すると次のような画像になります。

重なりから境界線を作成しています。データの開始点と終了点を考慮していないので所々直線でつないでしまっているところがあります。そこで、直線を削除する処理を行います。それが

$cityBorder = separateBorder($cityBorder,$max);//線が飛んでいるところを別の図形にする。

です。ここで先ほど求めた$maxを使っています。$max以上離れた点はありえない部分なのでその部分で図形を分けています。処理した結果は次図のようになります。

次に元のデータから市区町村の境界線を引くと県境が求まりますが、そのまま引くと県境に穴が開いてしまいます。そこで市区町村の境界線のデータの始点と終点の点データを削除します。それが次の処理です。本当は県境と接する側だけを処理したいのですが、その判断が面倒なので両端を短くする処理をしています。

  $cityBorder = cityBoarderEdge($cityBorder);//ここで端の線を1つ短くする処理をする。両側を短くする。

次に配列に余計な空白などのデータが入っていると後の処理でトラブルを起こすので削除しておきます。その処理が

  $cityBorder = arrayCleanup($cityBorder);//配列をきれいにする。

です。(これはいらないかも?)

次に$cityBorderを$coordinateのデータから引き算します。

$prefBorder = getPrefBorder($coordinate,$cityBorder);//すべてのデータcoordinateと市区町村の境界線から県境をもとめる。

結果は次の通りです。

市区町村の境界線を削除した影響と境界線の始点と終点の関係で直線や点データが残っています。それらを解決するために次の処理を行います。

$prefBorder = separateBorder($prefBorder,$max);//線が飛んでいる部分を処理する。
  $prefBorder = arrayCleanup($prefBorder);//配列をきれいにする。
  $prefBorder = deleteShortLine($prefBorder);//点が3つ以下の部分を削除
  $prefBorder = deleteEndLine($prefBorder,$ave);//図形の端っこの点が離れている場合には削除する。

結果こうなります。

佐渡が消えてますが先に説明した理由です。スクリーンショットを撮るのにIllustratorを使っているので図形がIllusratorの限界を超えてしまってるためです。

どこまで細かい部分まで入っているかというと新潟の東港部分を拡大してみます。

Google Mapでみると
https://www.google.co.jp/maps/@37.9922353,139.2383178,7064m/data=!3m1!1e3
といった感じになります。

47都道府県を連続で処理するとかなり時間がかかりますが、MacBook Pro (Core i5 2.9GHz)で実行した場合、次の結果になりました。合計で4時間ぐらいです。

    [hokkaido]exe Time=3296.7090649605
    [aomori]exe Time=122.16947507858
    [iwate]exe Time=1320.5427498817
    [miyagi]exe Time=1078.9129099846
    [akita]exe Time=123.61177301407
    [yamagata]exe Time=28.677213907242
    [fukushima]exe Time=32.269643068314
    [ibaraki]exe Time=6.4144871234894
    [tochigi]exe Time=1.9351642131805
    [gunnma]exe Time=2.7332141399384
    [saitama]exe Time=5.6779367923737
    [chiba]exe Time=81.843067884445
    [tokyo]exe Time=535.52261805534
    [kanagawa]exe Time=57.061334848404
    [niigata]exe Time=168.06734609604
    [toyama]exe Time=1.5160300731659
    [isihikawa]exe Time=62.458130121231
    [fukui]exe Time=124.91337704659
    [yamanashi]exe Time=2.8849470615387
    [nagano]exe Time=12.047899961472
    [gifu]exe Time=3.2795400619507
    [sizuoka]exe Time=212.89130711555
    [aiti]exe Time=66.630383968353
    [mie]exe Time=1041.8271570206
    [shiga]exe Time=0.64241909980774
    [kyoto]exe Time=44.150004148483
    [osaka]exe Time=5.5262830257416
    [hyougo]exe Time=185.11697602272
    [nara]exe Time=2.021488904953
    [wakayama]exe Time=263.70011901855
    [tottori]exe Time=18.710318803787
    [shimane]exe Time=237.44484496117
    [okayama]exe Time=19.264281988144
    [hiroshima]exe Time=37.061669111252
    [yamaguchi]exe Time=211.55726194382
    [tokushima]exe Time=168.82157993317
    [kagawa]exe Time=23.631745100021
    [ehime]exe Time=459.80693483353
    [kouchi]exe Time=241.01473593712
    [fukuoka]exe Time=137.41196608543
    [saga]exe Time=7.0379889011383
    [nagasaki]exe Time=2177.6520559788
    [kumamoto]exe Time=117.25712108612
    [ooita]exe Time=110.4707300663
    [miyazaki]exe Time=104.72366809845
    [kagoshima]exe Time=1371.7596409321
    [okinawa]exe Time=356.01933002472

そしてこちらの記事で紹介した
plotXML2JapanPrefBound.php
を使い一枚の日本地図(都道府県の境界線入り)に仕上げます。前の記事と今回でXMLのタグの名前が違ってたのでそこだけ修正が必要です。一応全文載せます。
plotXML2JapanPrefBound.php

<?php
//XMLフォルダに保存した各県のデータを読み取り日本の輪郭、県境のデータを書き出す。
//緯度経度のデータをstringのまま処理
//array_intersectを使い重なる緯度経度があるか判断。

ini_set('memory_limit', '2540M');

mb_language("Japanese");//文字コードの設定
mb_internal_encoding("UTF-8");

//ファイルリスト
$xmlFile[1] ="./PrefXML/hokkaido.xml";
$xmlFile[2] ="./PrefXML/aomori.xml";
$xmlFile[3] ="./PrefXML/iwate.xml";
$xmlFile[4] ="./PrefXML/miyagi.xml";
$xmlFile[5] ="./PrefXML/akita.xml";
$xmlFile[6] ="./PrefXML/yamagata.xml";
$xmlFile[7] ="./PrefXML/fukushima.xml";
$xmlFile[8] ="./PrefXML/ibaraki.xml";
$xmlFile[9] ="./PrefXML/tochigi.xml";
$xmlFile[10] ="./PrefXML/gunnma.xml";
$xmlFile[11] ="./PrefXML/saitama.xml";
$xmlFile[12] ="./PrefXML/chiba.xml";
$xmlFile[13] ="./PrefXML/tokyo.xml";
$xmlFile[14] ="./PrefXML/kanagawa.xml";
$xmlFile[15] ="./PrefXML/niigata.xml";
$xmlFile[16] ="./PrefXML/toyama.xml";
$xmlFile[17] ="./PrefXML/isihikawa.xml";
$xmlFile[18] ="./PrefXML/fukui.xml";
$xmlFile[19] ="./PrefXML/yamanashi.xml";
$xmlFile[20] ="./PrefXML/nagano.xml";
$xmlFile[21] ="./PrefXML/gifu.xml";
$xmlFile[22] ="./PrefXML/sizuoka.xml";
$xmlFile[23] ="./PrefXML/aiti.xml";
$xmlFile[24] ="./PrefXML/mie.xml";
$xmlFile[25] ="./PrefXML/shiga.xml";
$xmlFile[26] ="./PrefXML/kyoto.xml";
$xmlFile[27] ="./PrefXML/osaka.xml";
$xmlFile[28] ="./PrefXML/hyougo.xml";
$xmlFile[29] ="./PrefXML/nara.xml";
$xmlFile[30] ="./PrefXML/wakayama.xml";
$xmlFile[31] ="./PrefXML/tottori.xml";
$xmlFile[32] ="./PrefXML/shimane.xml";
$xmlFile[33] ="./PrefXML/okayama.xml";
$xmlFile[34] ="./PrefXML/hiroshima.xml";
$xmlFile[35] ="./PrefXML/yamaguchi.xml";
$xmlFile[36] ="./PrefXML/tokushima.xml";
$xmlFile[37] ="./PrefXML/kagawa.xml";
$xmlFile[38] ="./PrefXML/ehime.xml";
$xmlFile[39] ="./PrefXML/kouchi.xml";
$xmlFile[40] ="./PrefXML/fukuoka.xml";
$xmlFile[41] ="./PrefXML/saga.xml";
$xmlFile[42] ="./PrefXML/nagasaki.xml";
$xmlFile[43] ="./PrefXML/kumamoto.xml";
$xmlFile[44] ="./PrefXML/ooita.xml";
$xmlFile[45] ="./PrefXML/miyazaki.xml";
$xmlFile[46] ="./PrefXML/kagoshima.xml";
$xmlFile[47] ="./PrefXML/okinawa.xml";

$saveSVG = "./japanPrefBound.svg";


readXMLmakeOutline($xmlFile,$saveSVG);

function readXMLmakeOutline($xmlFile,$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("obj") 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の読み込み完了

  //=========================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

これで出来上がった日本地図がこちら。

277MBあるSVGになっています。上記の画像はそのスクリーンショットChromeで開いています。他のアプリでは開けませんでした。

ここから県の輪郭線を閉じた線にして、県と県の接するところに不都合が起きない様にデータ数を減らすと目的のデータが完成します。それはまた後ほど。