<?php
// TOBEDONE: HHA nutzt noch die Version auf dem Testserver!!!

include_once("../include/glob_defs.inc.php");
include_once("../include/global.inc.php");

function get_country_codes() {
  return array(
    "AL" => "Albania",
    "AT" => "Austria",
    "BE" => "Belgium",
    "BG" => "Bulgaria",
    "CH" => "Switzerland",
    "CZ" => "Czech Republic",
    "DE" => "Germany",
    "DK" => "Denmark",
    "ES" => "Spain",
    "FI" => "Finland",
    "FR" => "France",
    "GB" => "United Kingdom",
    "GR" => "Greece",
    "HR" => "Croatia",
    "HU" => "Hungary",
    "IE" => "Ireland",
    "IS" => "Iceland",
    "IT" => "Italy",
    "LI" => "Liechtenstein",
    "LT" => "Lithuania",
    "LU" => "Luxembourg",
    "LV" => "Latvia",
    "MC" => "Monaco",
    "MK" => "Macedonia",
    "NL" => "Netherlands",
    "NO" => "Norway",
    "PL" => "Poland",
    "PT" => "Portugal",
    "RO" => "Romania",
    "SE" => "Sweden",
    "SI" => "Slovenia",
    "SK" => "Slovakia",
    "UA" => "Ukraine");
}

function get_db_geo() {
  static $db_geo = NULL;

  if (is_null($db_geo)) {
    global $dblogin, $dbpassword;
    writeLog_xServer(basename($_SERVER['PHP_SELF']) . ": connecting to intern...");
    //$db_geo = getDbConnectionSpecial("172.16.0.110:3711", "address_geo", $dblogin, $dbpassword);
    $db_geo = getDbConnectionSpecial("172.16.0.111:3711", "address_geo", $dblogin, $dbpassword);
    $res = $db_geo->query('SET NAMES latin1');
    if (DB::isError($res)) reportDie ("$PHP_SELF: 'SET NAMES latin1' : " . $res->getMessage());
  }
  return $db_geo;
}
$db_plan = 0;

//$allCoords = array();
//$coords = XSfindAddress("D", "25469", "Halstenbek", "Friedrichshulder Weg", "81");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$allCoords[] = array($coords[0][1], $coords[0][2], 8*3600, 14*3600);
//$coords = XSfindAddress("D", "22848", "Norderstedt", "Ochsenzoller Straޥ", "31");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$coords = XSfindAddress("D", "25469", "Halstenbek", "Friedrichshulder Weg", "118");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$allCoords[] = array($coords[0][1], $coords[0][2], 12.5*3600, 19*3600);
//$coords = XSfindAddress("D", "21502", "Geesthacht", "Gerhart-Hauptmann-Weg", "14");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$allCoords[] = array($coords[0][1], $coords[0][2], 8*3600, 14*3600);
//$coords = XSfindAddress("D", "25421", "Pinneberg", "Oeltingsallee", "1b");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$allCoords[] = array($coords[0][1], $coords[0][2], 13*3600, 19*3600);
//$coords = XSfindAddress("D", "22041", "Hamburg", "Holzm�traޥ", "41");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$allCoords[] = array($coords[0][1], $coords[0][2], 8*3600, 14*3600);
//$coords = XSfindAddress("D", "22525", "Hamburg", "Ottensener Straޥ", "8");
//echo $coords[0][1] . ", " . $coords[0][2] . "<br>\n";
//$allCoords[] = array($coords[0][1], $coords[0][2], 13*3600, 19*3600);
//print_r(XSplanTimewindowTour($allCoords));

//XSplanTour(array(
//  array(843650, 4901137),
//  array(842164, 4901883),
//  array(843914, 4900754),
//  array(841020, 4900941),
//  array(841050, 4900578))
//);

//XSplanTourJob(16539796);
//XSplanTourJob(16539797, array(12.374671, 51.325363));
//XSplanTourJob(16539798);
//XSplanTourJob(16539799);
//XSplanTourJob(16539800);

function XSfindAddress($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno) {
  global $phpVersion;
  $db_geo = get_db_geo();

//  $XS_HOST = 'localhost';
/*
  $XS_HOST = '172.16.0.154';
  $XS_PORT = 50020;
  $XS_SERVICE = '/xlocate/ws/XLocate';
*/
 
  if (($ad_street == "3" && $ad_zipcode == "70180" && $ad_city == "Stuttgart") ||
      ($ad_street == "46/1" && $ad_zipcode == "71069" && $ad_city == "Sindelfingen") ||
      ($ad_street == "Hutt" && $ad_zipcode == "70597" && $ad_city == "Stuttgart" && $tr_hsno == "Kommunikat"))
    return array(0, 0.0, 0.0); 	 	
 
  $ad_country_clause = "ad.ad_country = '$ad_country'";
  if ($ad_country == "DE" || $ad_country == "D")
    $ad_country_clause = "(ad.ad_country = 'DE' OR ad.ad_country = 'D')";
   $sqlquery = "SELECT adg.ad_id, adg_gps_long, adg_gps_lat, adg_modify FROM address_geo.address AS ad, address_geo.address_geo AS adg WHERE " .
     "$ad_country_clause AND ad.ad_zipcode = '$ad_zipcode' AND ad.ad_city = '" . str_replace('\'', '\\\'', $ad_city) . "' AND ad.ad_street = '" . str_replace('\'', '\\\'', $ad_street) . "' " . 
     "AND ad.ad_id = adg.ad_id AND adg.adg_hsno = '$tr_hsno'";
//    "AND ad.ad_id = adg.ad_id AND adg.adg_hsno = '$tr_hsno' AND adg.adg_accuracy > 5";
//writeLog_xServer("XSfindAddress: " . $sqlquery);
  $res = $db_geo->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  $is_valid = false;
  $update_ad_id = 0;
  if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    $is_valid = check_expired($row["adg_modify"]);
    if (!$is_valid)
      $update_ad_id = $row["ad_id"];
  endif;
//writeLog_xServer("XSfindAddress: \$update_ad_id = " . $update_ad_id);
  if ($is_valid):
    $ret_code = 0;
    if ($row["adg_gps_long"] != 0 && $row["adg_gps_lat"] != 0):
      $ret_code = 100;
    endif;
    writeLog_xServer("XSfindAddress: [$ad_country $ad_zipcode " . utf8_encode($ad_city) . " " . utf8_encode($ad_street) . " " . utf8_encode($tr_hsno) . "] found in address_geo: " . $row["adg_gps_long"] . ", " . $row["adg_gps_lat"] . ", " . $row["adg_modify"]);
    $retA[] = array($ret_code, $row["adg_gps_long"], $row["adg_gps_lat"]);
  else:
    // Warte 0,75 Zehntel-Sekunden um "620 - too man queries" zu vermeiden
    usleep(75000);
    $row = check_address_google(($ad_country == "D" ? "DE" : $ad_country), $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $update_ad_id);
    $retA[] = array(($row[0] >= 6 ? 100 : 0), $row[2], $row[1]);
  endif;
  $res->free();
 
/*
  if ($ad_country == "DE")
    $ad_country = "D";
  $out = 
    '<?xml version="1.0" encoding="utf-8"?>' . "\n" .
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"' . "\n" .
    '    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . "\n" .
    '    xmlns:xsd="http://www.w3.org/2001/XMLSchema">' . "\n" .
    '  <soap:Body>' . "\n" .
    '    <findAddress xmlns="http://types.xlocate.xserver.ptvag.com">' . "\n" .
    '      <Address_1' . "\n" .
    '        country="'. check_xml_chars($ad_country) . '"' . "\n" .
    '        postCode="'. check_xml_chars($ad_zipcode) . '"' . "\n" .
    '        city="'. utf8_encode(check_xml_chars($ad_city)) . '"' . "\n" .
    '        city2=""' . "\n" .
    '        street="'. utf8_encode(check_xml_chars($ad_street)) . '"' . "\n" .
    '        houseNumber="'. utf8_encode(check_xml_chars($tr_hsno)) . '"' . "\n" .
    '      />' . "\n" .
    '      <ArrayOfSearchOptionBase_2 xsi:nil="true" />' . "\n" .
    '      <ArrayOfSortOption_3 xsi:nil="true" />' . "\n" .
    '      <ArrayOfResultField_4 xsi:nil="true" />' . "\n" .
    '      <CallerContext_5 log1="" log2="" log3="">' . "\n" .
    '        <wrappedProperties xmlns="http://baseservices.service.jabba.ptvag.com">' . "\n" .
    '          <CallerContextProperty key="CoordFormat" value="PTV_GEODECIMAL" />' . "\n" .
    '          <CallerContextProperty key="Profile" value="default" />' . "\n" .
    '        </wrappedProperties>' . "\n" .
    '      </CallerContext_5>' . "\n" .
    '    </findAddress>' . "\n" .
    '  </soap:Body>' . "\n" .
    '</soap:Envelope>' . "\n" .
    "\n";
    
  $res = xmlRequest($XS_HOST, $XS_PORT, $XS_SERVICE, $out);
  $vals = $res[0];
  $index = $res[1];
  
  $log_txt = "********************************************************************************\n" . $out;
  $retA = array();
  if (array_key_exists('SOAP:FAULT', $index)):
    echo $vals[$index['FAULTCODE'][0]]['value'] . " : " . $vals[$index['FAULTSTRING'][0]]['value'] . "\n";
    writeLog_xServer($log_txt);
    die();
  else:
    $log_txt .= "ERRORCODE=" . $vals[$index['RESULT'][0]]['attributes']['ERRORCODE'] . "\n";
    $i = 0;
    for ($i = 0, $j = 0; $i < count($index['NS6:POINT']); $i++, $j += 2):
      $log_txt .=  "\n";
      foreach ($vals[$index['NS2:RESULTADDRESS'][$j]]['attributes'] AS $attr => $val) {
        $log_txt .= "$attr: " . $val . "\n";
      }
      $score = $vals[$index['NS2:RESULTADDRESS'][$j]]['attributes']['TOTALSCORE'];
      $X = round($vals[$index['NS6:POINT'][$i]]['attributes']['X']) / 100000;
      $Y = round($vals[$index['NS6:POINT'][$i]]['attributes']['Y']) / 100000;
      $log_txt .= "Y, X = " . $X . ", " . $Y . "\n";
      $retA[] = array($score, $X, $Y);
      if ($score == 100 && count($retA) == 1):
        $ad_id = "";
        $sqlquery = "SELECT ad_id FROM address WHERE ad_street = '$ad_street' AND ad_zipcode = '$ad_zipcode' AND ad_city = '$ad_city' AND ad_country = '$ad_country'";
        $res = mysql_query($sqlquery, $mysql_db_geo) or die (mysql_error($mysql_db_geo));
        if ($row = mysql_fetch_array($res, MYSQL_ASSOC)):
          $ad_id = $row["ad_id"];
        endif;
        mysql_free_result($res);
        if ($ad_id == ""):
          $sqlquery = "INSERT INTO address (ad_street, ad_zipcode, ad_city, ad_country) VALUES ('$ad_street', '$ad_zipcode', '$ad_city', '$ad_country')";
          mysql_query($sqlquery, $mysql_db_geo) or die (mysql_error($mysql_db_geo));
          $ad_id = mysql_insert_id($mysql_db_geo);
        endif;
        $sqlquery = "INSERT INTO address_geo (ad_id, adg_hsno, adg_gps_long, adg_gps_lat, adg_accuracy) VALUES ('$ad_id', '$tr_hsno', '" . $X . "', '" . $Y . "', 8)";
        mysql_query($sqlquery, $mysql_db_geo) or die (mysql_error($mysql_db_geo));
      endif;
    endfor;
//    echo "Index array\n";
//    print_r($index);
//    echo "\nVals array\n";
//    print_r($vals);
  endif;
  writeLog_xServer($log_txt);
*/
  return $retA;
}

function XScheckAddress($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno)
{
  $coords = XSfindAddress($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno);
  if ($coords[0][0] != 100)
  	return false;
  return true;
}

//function XSplanTourSubOSM($stopCoordinates, $lastStop = 0){
//
//  $outStops = '';
//  foreach ($stopCoordinates as $idx => $pair) {
//    $outStops .= "&p" . $idx . "=" . ($pair[1] / 100000) . "," . ($pair[0] / 100000);
//  }
//  $request = "http://52.27.164.117:8080/mautkm/tsp.jsp?end=" . $lastStop . $outStops;
//  writeLog_xTsp($request);
////  echo $request; die();
//  $xml_answer = file_get_contents($request);
////  $xml_answer = "<tsp><points><point><latitud>51.379826</latitud><longitud>12.393204</longitud></point><point><latitud>51.354401</latitud><longitud>12.407575</longitud></point><point><latitud>51.352394</latitud><longitud>12.409649</longitud></point><point><latitud>51.35323</latitud><longitud>12.414073</longitud></point><point><latitud>51.345997</latitud><longitud>12.418421</longitud></point><point><latitud>51.344723</latitud><longitud>12.407607</longitud></point><point><latitud>51.341648</latitud><longitud>12.404552</longitud></point><point><latitud>51.343037</latitud><longitud>12.401502</longitud></point><point><latitud>51.346729</latitud><longitud>12.399704</longitud></point><point><latitud>51.345123</latitud><longitud>12.395965</longitud></point><point><latitud>51.345615</latitud><longitud>12.39314</longitud></point><point><latitud>51.33263</latitud><longitud>12.3978</longitud></point><point><latitud>51.331589</latitud><longitud>12.397505</longitud></point><point><latitud>51.328857</latitud><longitud>12.40392</longitud></point><point><latitud>51.333977</latitud><longitud>12.407532</longitud></point><point><latitud>51.336815</latitud><longitud>12.406914</longitud></point><point><latitud>51.333778</latitud><longitud>12.41003</longitud></point><point><latitud>51.33358</latitud><longitud>12.41629</longitud></point><point><latitud>51.33358</latitud><longitud>12.41629</longitud></point><point><latitud>51.322174</latitud><longitud>12.415061</longitud></point><point><latitud>51.321301</latitud><longitud>12.41415</longitud></point><point><latitud>51.314091</latitud><longitud>12.419854</longitud></point><point><latitud>51.313244</latitud><longitud>12.420699</longitud></point><point><latitud>51.293381</latitud><longitud>12.43579</longitud></point><point><latitud>51.324024</latitud><longitud>12.473988</longitud></point><point><latitud>51.351292</latitud><longitud>12.460631</longitud></point><point><latitud>51.353771</latitud><longitud>12.46084</longitud></point><point><latitud>51.356346</latitud><longitud>12.457835</longitud></point><point><latitud>51.392578</latitud><longitud>12.45708</longitud></point><point><latitud>51.382309</latitud><longitud>12.449062</longitud></point><point><latitud>51.355961</latitud><longitud>12.43026</longitud></point><point><latitud>51.378376</latitud><longitud>12.409581</longitud></point><point><latitud>51.384632</latitud><longitud>12.406139</longitud></point></points><url>http://52.27.164.117:8181/?point=51.379826,12.393204&point=51.354401,12.407575&point=51.352394,12.409649&point=51.35323,12.414073&point=51.345997,12.418421&point=51.344723,12.407607&point=51.341648,12.404552&point=51.343037,12.401502&point=51.346729,12.399704&point=51.345123,12.395965&point=51.345615,12.39314&point=51.33263,12.3978&point=51.331589,12.397505&point=51.328857,12.40392&point=51.333977,12.407532&point=51.336815,12.406914&point=51.333778,12.41003&point=51.33358,12.41629&point=51.33358,12.41629&point=51.322174,12.415061&point=51.321301,12.41415&point=51.314091,12.419854&point=51.313244,12.420699&point=51.293381,12.43579&point=51.324024,12.473988&point=51.351292,12.460631&point=51.353771,12.46084&point=51.356346,12.457835&point=51.392578,12.45708&point=51.382309,12.449062&point=51.355961,12.43026&point=51.378376,12.409581&point=51.384632,12.406139&locale=de-DE&vehicle=car&weighting=fastest&elevation=true&layer=OpenStreetMap.de</url></tsp>";
//  writeLog_xTsp($xml_answer);
//
//  $p = xml_parser_create();
//  xml_parse_into_struct($p, $xml_answer, $vals, $index);
//  xml_parser_free($p);
//
//  $retA = array();
//  $cnt = 1;
//  for ($i = 0; $i < count($index['LATITUD']); $i++) {
////    $log_txt .=  "\n";
//    foreach ($stopCoordinates as $idx => $pair) {
//      if ($vals[$index['LATITUD'][$i]]['value'] == (string)($pair[1] / 100000) && $vals[$index['LONGITUD'][$i]]['value'] == (string)($pair[0] / 100000)) {
//      	if (!in_array(($idx + 1), $retA)) {
//          $retA[] = $idx + 1;
//          writeLog_xTsp($cnt++ . ": " . ($idx + 1) . " (" . $vals[$index['LONGITUD'][$i]]['value'] . ", " . $vals[$index['LATITUD'][$i]]['value'] . ", " . $pair[2] . ")");
//          break;
//        }
//      }
//    }
//  }
////echo json_encode($retA);
//  return $retA;
//}

function XSplanTourRequest($stopCoordinates) {

  $outStops = '';
  foreach ($stopCoordinates as $idx => $pair)
    $outStops .= ($outStops != "" ? "|" : "") . $pair[0] . "," . $pair[1];

  $request = "https://ext01.sb-test.assecutor.de/service/xTsp.php?stops=" . $outStops;
  writeLog_xTsp($request);
  $json_answer = file_get_contents($request);
  writeLog_xTsp($json_answer);
  $json_answerA = json_decode($json_answer, true);
  $opt_stops = explode(",", $json_answerA["opt_stops"]);
  foreach ($opt_stops as $idx)
    $retA[] = $idx;
  return $retA;
}

function XSplanTourORTools($stopCoordinates, $lastStop = 0){
  $stopCoordinatesDistinct = array();
  for ($i = 0; $i < count($stopCoordinates); $i++) {
    $stopCoordinatesDistinct[(round($stopCoordinates[$i][1] * 1000000) / 1000000) . "," . (round($stopCoordinates[$i][0] * 1000000) / 1000000)][] = $i;
  }
  $outStops = '';
  $outStopsArr = array();
  $i = 0;
//  foreach ($stopCoordinates as $idx => $pair) {
  foreach ($stopCoordinatesDistinct as $pair => $indexes) {
//    $outStops .= ($outStops != "" ? "|" : "") . ($pair[1] / 100000) . "," . ($pair[0] / 100000);
    $outStops .= ($outStops != "" ? "|" : "") . $pair;
    $outStopsArr[$i++] = $pair;
  }

  $distances = get_distances_from_db($outStops, $outStops);

  $distance_matrix_str = "";
  for ($i = 0; $i < count($distances); $i++) {
    $distance_matrix_str .= ($distance_matrix_str != "" ? " " : "") . $distances[$i][4];
  }

  writeLog_xTsp('python3 ../locating/tsp.py ' . $distance_matrix_str);
  $output=null;
  $retval=null;
  exec('python3 ../locating/tsp.py ' . $distance_matrix_str, $output, $retval);
  writeLog_xTsp($output[0]);
  $resultArr = explode(",", $output[0]);
  $gogleMapsLink = "";
  $cnt = 0;
  for ($i = 0; $i < count($resultArr); $i++) {
//    $retA[] = $resultArr[$i] + 1;
//    writeLog_xTsp(($i + 1) . ": " . ($resultArr[$i] + 1) . " (" . ($stopCoordinates[$resultArr[$i]][0] / 100000) . ", " . ($stopCoordinates[$resultArr[$i]][1] / 100000) . ")");
    foreach ($stopCoordinatesDistinct[$outStopsArr[$resultArr[$i]]] as $idx) {
      $retA[] = $idx + 1;
      writeLog_xTsp((++$cnt) . ": " . ($idx + 1) . " (" . $outStopsArr[$resultArr[$i]] . ")");
    }
    $gogleMapsLink .= "/" . $outStopsArr[$i];
  }

  writeLog_xTsp("https://www.google.de/maps/dir" . $gogleMapsLink);

  return $retA;
}

function XSplanTourStb978440($stopCoordinates){
  $zipcode_order = array(
    // Tour 0 Reihenfolge ohne Umland
    array("13593","14089","13595","13581","13591","13583","13589","13587","13585","13597","14050","14052","14053","14055","14057","14059","13599","13629","13627","13351","10553","10551",
      "10555", "10557","10559","13353","13347","13355","13357","13359","13409","13407","13349","13405","13507","13403","13509","13437","13439","13435","13469","13467","13465","13503","13505"),
    // Tour 0 Reihenfolge mit Umland (zusätzlich 14612, 16727)
    // - diese muss an zweiter Stelle stehen, damit die vorhergehende Reihenfolge als gültig erkannt wird, wenn die zusätzlichen PLZs 14612 16727 _nicht_ vorhanden sind
    array("13593","14089","13595","13597","13599","13587","13585","13589","13583","13581","13591","14612","16727","13505","13503","13465","13467","13469","13435","13439","13437","13509","13507",
      "13405","13403","13407","13409","13359","13357","13355","13347","13353","13349","13351","10553","10555","10557","10559","10551","13627","13629","14050","14059","14057","14055","14052","14053")
  );
  $stopCoordinatesSorted = array();
  for ($i = 0; $i < count($zipcode_order); $i++) {
  	if (count($stopCoordinatesSorted) == 0) {
      for ($j = 0; $j < count($stopCoordinates); $j++) {
        $key = array_search($stopCoordinates[$j][2], $zipcode_order[$i]);
        if ($key !== false) {
          $stopCoordinatesSorted[$j] = $key * 1000 + $j; // "* 1000 + $j" damit doppelte PLZs eindeutige Rangfolgennummern bekommen
        } else {
          $stopCoordinatesSorted = array();
          break;
        }
      }
    }
  }
writeLog_xTsp("\$stopCoordinates = " . json_encode($stopCoordinates));
//writeLog_xTsp("\$stopCoordinatesSorted = " . json_encode($stopCoordinatesSorted));
  asort($stopCoordinatesSorted);
//writeLog_xTsp("\$stopCoordinatesSorted = " . json_encode($stopCoordinatesSorted));
  $stopCoordinatesSorted = array_keys($stopCoordinatesSorted);
//writeLog_xTsp("\$stopCoordinatesSorted = " . json_encode($stopCoordinatesSorted));
  for ($i = 0; $i < count($stopCoordinatesSorted); $i++)
    $stopCoordinatesSorted[$i]++;
writeLog_xTsp("\$stopCoordinatesSorted = " . json_encode($stopCoordinatesSorted));
  return ($stopCoordinatesSorted);
}

function XScheckAddresses($jb_id_plan) {
  global $db_plan, $phpVersion;

  $sqlquery = "SELECT " . /*tr_comp,*/ "address.ad_id, ad_street, ad_city FROM job, tour, address WHERE job.jb_id = " . $jb_id_plan . " AND job.jb_id = tour.jb_id AND tour.ad_id = address.ad_id";
  $res1 = $db_plan->query($sqlquery);
  if (DB::isError($res1)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res1->getMessage());
  while ($row1 = ($phpVersion >= "8.0" ? $res1->fetch_assoc() : $res1->fetchRow())):
//    $tr_comp_new = XScheckString($row1["tr_comp"]);
//echo $row1["tr_comp"] . "\n";
//echo $tr_comp_new . "\n";
    $ad_street_new = XScheckString($row1["ad_street"]);
//echo $row1["ad_street"] . "\n";
//echo $ad_street_new . "\n";
    $ad_city_new =  XScheckString($row1["ad_city"]);
//echo $row1["ad_city"] . "\n";
//echo $ad_city_new . "\n\n";
    if ($ad_street_new != $row1["ad_street"] || $ad_city_new != $row1["ad_city"]):
      writeLog_xTsp("UPDATE address SET ad_street = '" . $ad_street_new . "', ad_city = '" . $ad_city_new . "' WHERE ad_id = " . $row1["ad_id"]);
      exec_query_db_plan("UPDATE address SET ad_street = '" . $ad_street_new . "', ad_city = '" . $ad_city_new . "' WHERE ad_id = " . $row1["ad_id"]);
    endif;
  endwhile;
  $res1->free();
}

function XScheckString($the_string) {
  // check for broken umlauts (DOS Ascii -> ANSI) causing google not to find the correct address
//echo ord(chr(0x8E)) . " " . ord(chr(0x99)) . " " . ord(chr(0x9A)) . " " . ord(chr(0x84)) . " " . ord(chr(0x94)) . " " . ord(chr(0x81)) . " " . ord(chr(0xE1)) . "\n";
//for ($i = 0; $i < strlen($the_string); $i++)
//  echo ord($the_string[$i]) . " ";
//echo "\n";
  return(
    str_replace(chr(0x8E) /*'Ž'*/, chr(196), //chr(ord("Ä")),
    str_replace(chr(0x99) /*'™'*/, chr(214), //chr(ord("Ö")),
    str_replace(chr(0x9A) /*'š'*/, chr(220), //chr(ord("Ü")),
    str_replace(chr(0x84) /*'„'*/, chr(228), //chr(ord("ä")),
    str_replace(chr(0x94) /*'”'*/, chr(246), //chr(ord("ö")),
    str_replace(chr(0x81) /*' '*/, chr(252), //chr(ord("ü")),
    str_replace(chr(0xE1) /*'á'*/, chr(223), //chr(ord("ß")),
    str_replace(chr(0xDF) /*'á'*/, chr(223), //chr(ord("ß")),
    preg_replace("/([\xC2\xC3])([\x80-\xBF])/e", "chr(ord('\\1')<<6&0xC0|ord('\\2')&0x3F)", $the_string)
    ))))))))
  );
}

function XSplanTourJob($jb_id_plan, $end_coordinates = array(), $start_coordinates = array())
{
  global $db_plan, $dbhost, $dblogin, $dbpassword, $phpVersion;

  writeLog_xTsp("\nXSplanTourJob(" . $jb_id_plan . ", " . json_encode($end_coordinates) . ", " . json_encode($start_coordinates) . ")");

  $db_plan = getDbConnectionSpecial($dbhost, "phoenix", $dblogin, $dbpassword);
  $res = $db_plan->query('SET NAMES latin1');
  if (DB::isError($res)) reportDie ("$PHP_SELF: 'SET NAMES latin1' : " . $res->getMessage());

  // Sonderlocke für Carsystem Farberlin GmbH (STB978440)
  $optimizationPossible = true;
  $stb978440 = false;
  $sqlquery = "SELECT cs_eid FROM customer, costcenter, job WHERE customer.cs_id = costcenter.cs_id AND costcenter.csc_id = job.csc_id_payer AND jb_id = " . $jb_id_plan;
  $res = $db_plan->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    if ($row["cs_eid"] == "STB978440"):
      XScheckAddresses($jb_id_plan);
      $stb978440 = true;
    endif;
  endif;
  $res->free();

  $stopCoordinates = array();
  $sqlquery = "SELECT ad_country, ad_zipcode, ad_city, ad_street, tr_hsno FROM tour As tr, address AS ad WHERE jb_id = $jb_id_plan AND tr.ad_id = ad.ad_id AND tr_ware_from_to IN (0,1) ORDER BY tr_sort";
  $res = $db_plan->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  $i = 0;  
  while ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
//    if ($row["tr_ware_from_to"] == 1):
      $coords = XSfindAddress("D", str_replace('\'', '\\\'', $row["ad_zipcode"]), str_replace('\'', '\\\'', $row["ad_city"]), str_replace('\'', '\\\'', $row["ad_street"]), str_replace('\'', '\\\'', $row["tr_hsno"]));
      writeLog_xTsp("[" . str_replace('\'', '\\\'', $row["ad_zipcode"]) . " " . str_replace('\'', '\\\'', $row["ad_city"]) . " " . str_replace('\'', '\\\'', $row["ad_street"]) . " " . str_replace('\'', '\\\'', $row["tr_hsno"]) . "] [" . $coords[0][0] . ", " . $coords[0][1] . ", " . $coords[0][2] . "]");
      if ($coords[0][0] != 100):
//         echo " kein exakter Match<br>\n"; flush();
//         echo "<b>Routenoptmierung nicht m&ouml;glich!</b><br>\n";
        writeLog_xTsp("no coordinates found, optimization not possible");
        $optimizationPossible = false;
        if (!$stb978440)
          return false;
//         die();
//      else:
//         echo " ok<br>\n"; flush();
      endif;
//    else:
//      $sqlquery = "SELECT plzg_laenge, plzg_breite FROM phoenix_special.plz_geodb WHERE plzg_code = '" . $row["ad_zipcode"] . "'";
////writeLog_xTsp("[" . $sqlquery . "\n");
//      $res1 = mysql_query($sqlquery, $mysql_db_plan) or die ($sqlquery . ": " . mysql_error($mysql_db_plan));
//      if ($row1 = mysql_fetch_array($res1, MYSQL_ASSOC)):
//        $coords = array(array(100, $row1["plzg_laenge"], $row1["plzg_breite"]));
//        writeLog_xTsp("[" . $row["ad_zipcode"] . "] found in plz_geodb: " . $row1["plzg_laenge"] . ", " . $row1["plzg_breite"] . "\n");
//      else:
//        echo "Finde keine Koordinaten f&uuml;r PLZ '" . $row["ad_zipcode"] . "'<br>\n"; flush();
//        echo "<b>Routenoptmierung nicht m&ouml;glich!</b><br>\n";
//        die();
//      endif;
//      mysql_free_result($res1);
//    endif;
    $stopCoordinates[$i++] = array($coords[0][1] * 100000, $coords[0][2] * 100000, $row["ad_zipcode"] /*letzters eigentlich nur für STB978440*/);

//    if (count($end_coordinates) == 0 && floor($coords[0][1] * 100000) == 1317846 && floor($coords[0][2] * 100000) == 5251742) // Sonderlocke für Carsystem Farberlin GmbH (STB978440)
//      $end_coordinates = array(13.377, 52.516);
  endwhile;
  $res->free();
  
  if (count($stopCoordinates) < 3)
    return true;
//print_r($stopCoordinates);
//die();
  $subtractor = 0;
  if (count($end_coordinates) == 2):
    $stopCoordinates[$i++] = array($end_coordinates[0] * 100000, $end_coordinates[1] * 100000);
    $subtractor = 1;
  endif;
  if (count($start_coordinates) == 2):
    $stopCoordinates[0] = array($start_coordinates[0] * 100000, $start_coordinates[1] * 100000);
  endif;
//(nur PTV)  $planTour = XSplanTourSub($stopCoordinates, $subtractor);

  // Sonderlocke für Carsystem Farberlin GmbH (STB978440) ?
  if (!$stb978440):
    $planTour = XSplanTourORTools($stopCoordinates, $subtractor);
  else:
    $planTour = XSplanTourStb978440($stopCoordinates);
    if (count($planTour) == 0 && $optimizationPossible)
      $planTour = XSplanTourORTools($stopCoordinates, $subtractor);
  endif;

  if (count($planTour) - $subtractor > 0):
    // bestehenden tr_sort mit 100 addieren
    for ($i = 0; $i < count($planTour) - $subtractor; $i++) {
	  exec_query_db_plan("UPDATE tour SET tr_sort = " . (500 + $i + 1) . " WHERE jb_id = $jb_id_plan AND tr_sort = " . ($i+1));
      exec_query_db_plan("UPDATE tourarticle SET tr_sort = " . (500 + $i + 1) . " WHERE jb_id = $jb_id_plan AND tr_sort = " . ($i+1));
	    
    }  
    // jetzt k�n die neuen tr_sort geschrieben werden
    for ($i = 0; $i < count($planTour); $i++) {
	  exec_query_db_plan("UPDATE tour SET tr_sort = " . ($i + 1) . " WHERE jb_id = $jb_id_plan AND tr_sort = " . (500 + $planTour[$i]));
      exec_query_db_plan("UPDATE tourarticle SET tr_sort = " . ($i + 1) . " WHERE jb_id = $jb_id_plan AND tr_sort = " . (500 + $planTour[$i]));
    }
    XSmk_jb_tourdata($jb_id_plan);
    return true;
  endif;
  return false;
}

function XSmk_jb_tourdata($jb_id) {
  global $db_plan, $phpVersion;
//  global $mysql_db_plan, $dbhost, $dblogin, $dbpassword;

//  $mysql_db_plan = mysql_connect($dbhost, $dblogin, $dbpassword) or die (mysql_error($mysql_db_plan));
//  mysql_select_db("phoenix", $mysql_db_plan) or die (mysql_error($mysql_db_plan));
//  mysql_query('SET NAMES latin1', $mysql_db_plan) or die ('SET NAMES latin1' . ": " . mysql_error($mysql_db_plan));

  $jb_tourdata1 = "";
  $jb_tourdata2 = "";
  $jb_tourdata3 = "";
  $sqlquery = "SELECT ad_zipcode, ad_city, ad_country FROM address, tour WHERE jb_id = " . $jb_id . " AND address.ad_id = tour.ad_id ORDER BY tr_sort";
  $res = $db_plan->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  while ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    $jb_tourdata1 .= ($jb_tourdata1 == "" ? "" : ";") . $row['ad_zipcode'];
    $jb_tourdata2 .= ($jb_tourdata2 == "" ? "" : ";") . $row['ad_city'];
    $jb_tourdata3 .= ($jb_tourdata3 == "" ? "" : ";") . $row['ad_country'];
  endwhile;
  $res->free();
  exec_query_db_plan("UPDATE job SET jb_tourdata = '" . $jb_tourdata1 . "|" . $jb_tourdata2 . "|" . $jb_tourdata3 . "' WHERE jb_id = " . $jb_id);
}

function XSplanTourId($stopCoordinatesId, $lastStop = 0)
{
  writeLog_xTsp("");
  writeLog_xTsp("\$stopCoordinatesId = ". json_encode($stopCoordinatesId));

  $stopCoordinates = array();
  $stopCoordinatesRet = array();
  
  for ($i = 0; $i < count($stopCoordinatesId) - $lastStop; $i++):
    $stopCoordinates[$i] = array($stopCoordinatesId[$i][1], $stopCoordinatesId[$i][2]);
  endfor;
  $planTour = XSplanTourRequest($stopCoordinates);

  if (count($planTour) - $lastStop > 0):
   for ($i = 0; $i < count($planTour) - $lastStop; $i++):
      $stopCoordinatesRet[$i] = $stopCoordinatesId[$planTour[$i] - 1];
    endfor;
  else:
    $stopCoordinatesRet = $stopCoordinatesId;
  endif;
  writeLog_xTsp("\$stopCoordinatesRet = " . json_encode($stopCoordinatesRet));
  return ($stopCoordinatesRet);
}

/*
function XSplanTourSub($stopCoordinates, $lastStop = 0)
{
//  $XS_HOST = 'localhost';
  $XS_HOST = '172.16.0.154';
  $XS_PORT = 50050;
  $XS_SERVICE = '/xsequence/ws/XSequence';

  $outStops = '';
  $subStops = '';
  foreach ($stopCoordinates as $idx => $pair) {
    $outStops .=
      '    <ns2:Stop servicePeriod="0" stopID="' . ($idx + 1) . '" xsi:type="ns2:Stop">' . "\n" .
      '      <ns2:loc x="' . round($pair[0]) . '" y="' . round($pair[1]) . '"/>' . "\n" .
      '    </ns2:Stop>' . "\n";
    $subStops .=  '          <ns2:InputTourPoint stopID="' . ($idx + 1) . '"/>' . "\n";
  }

//  $planningParams->costDistanceKm = 10;
//  $planningParams->costPeriodMinute = 50;
//  $planningParams->maxProcessorPeriod = 5;
//  $planningParams->distanceCalculation->dimaCoordFormat = 'GEODECIMAL';

  $out = 
    '<?xml version="1.0" encoding="utf-8"?>' . "\n" .
    '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"' .
    ' xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"' .
    ' xmlns:ns0="http://exception.core.jabba.ptvag.com"' .
    ' xmlns:ns1="http://common.xserver.ptvag.com"' .
    ' xmlns:ns2="http://xsequence.xserver.ptvag.com"' .
    ' xmlns:ns3="http://types.xsequence.xserver.ptvag.com"' .
    ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' .
    ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . "\n" .
    '  <env:Body>' . "\n" .
    '    <ns3:planTour>' . "\n" .
    '      <ns3:ArrayOfStop_1>' . "\n" .
    $outStops .
    '      </ns3:ArrayOfStop_1>' . "\n" .
    '      <ns3:PlanningParams_2 xsi:type="ns2:PlanningParams">' . "\n" .
    '        <ns2:distanceCalculation dimaCoordFormat="GEODECIMAL" drivingPeriodFactor="1.0" geometricSpeed="60"/>' . "\n" .
    '      </ns3:PlanningParams_2>' . "\n" .
    '      <ns3:InputTour_3 xsi:type="ns2:InputTour">' . "\n" .
    '        <ns2:wrappedInputTourPoints>' . "\n" .
    $subStops .
    '        </ns2:wrappedInputTourPoints>' . "\n" .
    '        <ns2:subSequence predPos="1" succPos="' . (count($stopCoordinates) + 1 - $lastStop) . '"/>' . "\n" .
    '      </ns3:InputTour_3>' . "\n" .
    '      <ns3:CallerContext_4 xsi:nil="1"/>' . "\n" .
    '    </ns3:planTour>' . "\n" .
    '  </env:Body>' . "\n" .
    '</env:Envelope>' . "\n" .
    "\n";
//echo $out;

  $lock_text = "planTour is running";
  while (file_get_contents("../log/xServer_planTour_lock.txt") == $lock_text):
    writeLog_xServer("waiting for another running optimization to terminate...");
    sleep(1);
  endwhile;
  $fileHandle = @fopen("../log/xServer_planTour_lock.txt", 'w'); @fwrite($fileHandle, $lock_text); @fclose($fileHandle);
  $res = xmlRequest($XS_HOST, $XS_PORT, $XS_SERVICE, $out);
  $fileHandle = @fopen("../log/xServer_planTour_lock.txt", 'w'); @fwrite($fileHandle, ""); @fclose($fileHandle);
  $vals = $res[0];
  $index = $res[1];

  $log_txt = "********************************************************************************\n" . $out;
  $retA = array();
  if (array_key_exists('SOAP:FAULT', $index)) {
    echo $vals[$index['FAULTCODE'][0]]['value'] . " : " . $vals[$index['FAULTSTRING'][0]]['value'] . "\n";
    die();
  } else {  
    $log_txt .= "RESULT=" . $vals[$index['NS2:RESULT'][0]]['attributes']['RESULT'] . "\n\n";
    foreach ($vals[$index['NS17:TOUR'][0]]['attributes'] AS $attr => $val) {
      $log_txt .= "$attr: $val\n";
    }
    foreach ($index['NS17:OUTPUTTOURPOINT'] AS $otp) {
      $log_txt .=  "\n";
      $retA[] = $vals[$otp]['attributes']['STOPID'];
      foreach ($vals[$otp]['attributes'] AS $attr => $val) {
        $log_txt .= "$attr: $val\n";
      }
    }
//    echo "Index array\n";
//    print_r($index);
//    echo "\nVals array\n";
//    print_r($vals);
  }
  writeLog_xServer($log_txt);
  return $retA;
}

function XSplanTour($stopCoordinates)
{
//  $XS_HOST = 'localhost';
  $XS_HOST = '172.16.0.154';
  $XS_PORT = 50050;
  $XS_SERVICE = '/xsequence/ws/XSequence';

  $outStops = '';
  foreach ($stopCoordinates as $idx => $pair) {
    $outStops .=
      '    <ns2:Stop servicePeriod="0" stopID="' . ($idx + 1) . '" xsi:type="ns2:Stop">' . "\n" .
      '      <ns2:loc x="' . round($pair[0]) . '" y="' . round($pair[1]) . '"/>' . "\n" .
      '    </ns2:Stop>' . "\n";
  }

//  $planningParams->costDistanceKm = 10;
//  $planningParams->costPeriodMinute = 50;
//  $planningParams->maxProcessorPeriod = 5;
//  $planningParams->distanceCalculation->dimaCoordFormat = 'GEODECIMAL';

  $out = 
    '<?xml version="1.0" encoding="utf-8"?>' . "\n" .
    '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"' .
    ' xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"' .
    ' xmlns:ns0="http://exception.core.jabba.ptvag.com"' .
    ' xmlns:ns1="http://common.xserver.ptvag.com"' .
    ' xmlns:ns2="http://xsequence.xserver.ptvag.com"' .
    ' xmlns:ns3="http://types.xsequence.xserver.ptvag.com"' .
    ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' .
    ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . "\n" .
    '  <env:Body>' . "\n" .
    '    <ns3:planTour>' . "\n" .
    '      <ns3:ArrayOfStop_1>' . "\n" .
    $outStops .
    '      </ns3:ArrayOfStop_1>' . "\n" .
    '      <ns3:PlanningParams_2 xsi:type="ns2:PlanningParams">' . "\n" .
    '        <ns2:distanceCalculation dimaCoordFormat="GEODECIMAL" drivingPeriodFactor="1.0" geometricSpeed="60"/>' . "\n" .
    '      </ns3:PlanningParams_2>' . "\n" .
    '      <ns3:InputTour_3 xsi:nil="1"/>' . "\n" .
    '      <ns3:CallerContext_4 xsi:nil="1"/>' . "\n" .
    '    </ns3:planTour>' . "\n" .
    '  </env:Body>' . "\n" .
    '</env:Envelope>' . "\n" .
    "\n";
//echo $out;

  $res = xmlRequest($XS_HOST, $XS_PORT, $XS_SERVICE, $out);
  $vals = $res[0];
  $index = $res[1];

  $log_txt = "********************************************************************************\n" . $out;
  $retA = array();
  if (array_key_exists('SOAP:FAULT', $index)) {
    echo $vals[$index['FAULTCODE'][0]]['value'] . " : " . $vals[$index['FAULTSTRING'][0]]['value'] . "\n";
    die();
  } else {  
    $log_txt .= "RESULT=" . $vals[$index['NS2:RESULT'][0]]['attributes']['RESULT'] . "\n\n";
    foreach ($vals[$index['NS17:TOUR'][0]]['attributes'] AS $attr => $val) {
      $log_txt .= "$attr: $val\n";
    }
    foreach ($index['NS17:OUTPUTTOURPOINT'] AS $otp) {
      $log_txt .=  "\n";
      $retA[] = $vals[$otp]['attributes']['STOPID'];
      foreach ($vals[$otp]['attributes'] AS $attr => $val) {
        $log_txt .= "$attr: $val\n";
      }
    }
//    echo "Index array\n";
//    print_r($index);
//    echo "\nVals array\n";
//    print_r($vals);
  }
  writeLog_xServer($log_txt);
  return $retA;
}

function XSplanTimewindowTour($stopCoordinates, $predPos = 0, $succPos = 0)
{
//  $XS_HOST = 'localhost';
  $XS_HOST = '172.16.0.154';
  $XS_PORT = 50050;
  $XS_SERVICE = '/xsequence/ws/XSequence';

//  if $predPos == 0
  if ($succPos == 0)
    $succPos = count($stopCoordinates) + 1;
  $outStops = '';
  $subStops = '';
  foreach ($stopCoordinates as $idx => $pairs) {
    $outStops .=
      '        <ns2:TimewindowStop stopID="' . ($idx + 1) . '" xsi:type="ns2:TimewindowStop">' . "\n" .
//      '        <ns2:TimewindowStop servicePeriod="900" stopID="' . ($idx + 1) . '" xsi:type="ns2:TimewindowStop">' . "\n" .
      '          <ns2:loc x="' . round($pairs[0]) . '" y="' . round($pairs[1]) . '"/>' . "\n" .
      '          <ns2:wrappedOpeningIntervals>' . "\n" .
      '            <ns2:Interval from="' . $pairs[2] . '" till="' . $pairs[3] . '"/>' . "\n" .
      '          </ns2:wrappedOpeningIntervals>' . "\n" .
      '        </ns2:TimewindowStop>' . "\n";
    $subStops .=  '          <ns2:InputTourPoint stopID="' . ($idx + 1) . '"/>' . "\n";
  }

//  $planningParams->costDistanceKm = 10;
//  $planningParams->costPeriodMinute = 50;
//  $planningParams->maxProcessorPeriod = 5;
//  $planningParams->distanceCalculation->dimaCoordFormat = 'GEODECIMAL';

  $out = 
    '<?xml version="1.0" encoding="utf-8"?>' . "\n" .
    '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"' .
    ' xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/"' .
    ' xmlns:ns0="http://exception.core.jabba.ptvag.com"' .
    ' xmlns:ns1="http://common.xserver.ptvag.com"' .
    ' xmlns:ns2="http://xsequence.xserver.ptvag.com"' .
    ' xmlns:ns3="http://types.xsequence.xserver.ptvag.com"' .
    ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' .
    ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . "\n" .
    '  <env:Body>' . "\n" .
    '    <ns3:planTimewindowTour>' . "\n" .
    '      <ns3:ArrayOfTimewindowStop_1>' . "\n" .
    $outStops .
    '      </ns3:ArrayOfTimewindowStop_1>' . "\n" .
    '      <ns3:TimewindowPlanningParams_2 xsi:type="ns2:TimewindowPlanningParams">' . "\n" .
    '        <ns2:distanceCalculation dimaCoordFormat="GEODECIMAL" drivingPeriodFactor="1.0"/>' . "\n" .
    '        <ns2:wrappedOperatingIntervals>' . "\n" .
    '          <ns2:Interval from="25200" till="72000"/>' . "\n" .
    '        </ns2:wrappedOperatingIntervals>' . "\n" .
    '      </ns3:TimewindowPlanningParams_2>' . "\n" .
    '      <ns3:InputTour_3 xsi:type="ns2:InputTour">' . "\n" .
    '        <ns2:wrappedInputTourPoints>' . "\n" .
    $subStops .
    '        </ns2:wrappedInputTourPoints>' . "\n" .
    '        <ns2:subSequence predPos="' . $predPos . '" succPos="' . $succPos . '"/>' . "\n" .
    '      </ns3:InputTour_3>' . "\n" .
    '      <ns3:CallerContext_4 xsi:nil="1"/>' . "\n" .
    '    </ns3:planTimewindowTour>' . "\n" .
    '  </env:Body>' . "\n" .
    '</env:Envelope>' . "\n" .
    "\n";
//echo $out;

  $res = xmlRequest($XS_HOST, $XS_PORT, $XS_SERVICE, $out);
  $vals = $res[0];
  $index = $res[1];

  $log_txt = "********************************************************************************\n" . $out;
  $retA = array();
  if (array_key_exists('SOAP:FAULT', $index)) {
    $log_txt .= $vals[$index['FAULTCODE'][0]]['value'] . " : " . $vals[$index['FAULTSTRING'][0]]['value'] . "\n";
//    die();
  } else {  
    $log_txt .= "RESULT=" . $vals[$index['NS2:RESULT'][0]]['attributes']['RESULT'] . "\n\n";
    foreach ($vals[$index['NS17:TOUR'][0]]['attributes'] AS $attr => $val) {
      $log_txt .= "$attr: $val\n";
    }
    for ($i = 0; $i < count($index['NS17:OUTPUTTOURPOINT']); $i += 2) {
      $otp = $index['NS17:OUTPUTTOURPOINT'][$i];
      $log_txt .=  "\n";
      $retA[] = $vals[$otp]['attributes']['STOPID'];
      foreach ($vals[$otp]['attributes'] AS $attr => $val) {
        $log_txt .= "$attr: $val\n";
      }
    }
//    echo "Index array\n";
//    print_r($index);
//    echo "\nVals array\n";
//    print_r($vals);
  }
  writeLog_xServer($log_txt);
  return $retA;
}

function XSplanTourJobAmazon($jb_id_plan, $predPos = 0, $succPos = 0)
{
  global $db, $phpVersion;

  $stopCoordinates = array();
  $sqlquery = "SELECT tr_comp, ad_country, ad_zipcode, ad_city, ad_street, tr_hsno FROM tour As tr, address AS ad WHERE jb_id = $jb_id_plan AND tr.ad_id = ad.ad_id ORDER BY tr_sort";  

  $res = $db->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery'" . $res->getMessage());
  $i = 0;
  while ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    $interval = array(8*3600, 19*3600);
    if ($row["tr_comp"] == "STBX_TIMED")
      $interval = array(8*3600, 12*3600);
    $coords = XSfindAddress("D", $row["ad_zipcode"], $row["ad_city"], $row["ad_street"], $row["tr_hsno"]);
    if ($coords[0][0] != 100) {
       echo " kein exakter Match<br>\n"; flush();
       echo "<b>Routenoptmierung nicht m&ouml;glich!</b><br>\n";
       die();
    }
    $stopCoordinates[$i++] = array($coords[0][1] * 100000, $coords[0][2] * 100000, $interval[0], $interval[1]);
  endwhile;
  $res->free();

  $stopIt = false;
  do {
    $planTour = XSplanTimewindowTour($stopCoordinates, $predPos, $succPos);
    if (count($planTour) == 0):
      for ($i = 1; $i < count($stopCoordinates); $i++):
        $stopCoordinates[$i][2] -= 300;
        $stopCoordinates[$i][3] += 300;
        if ($stopCoordinates[$i][2] < 0 OR $stopCoordinates[$i][3] > 86400):
          $stopIt = true;
          break;
        endif;
      endfor;
    endif;
  } while (count($planTour) == 0 && !$stopIt);

  if (count($planTour) > 0):
    // bestehenden tr_sort mit 100 addieren
    for ($i = 0; $i < count($planTour); $i++) {
      updateStmt("tour", "jb_id", $jb_id_plan, array("tr_sort", 100 + $i + 1), "tr_sort = " . ($i+1));
    }  
    // jetzt können die neuen tr_sort geschrieben werden
    for ($i = 0; $i < count($planTour); $i++) {
      updateStmt("tour", "jb_id", $jb_id_plan, array("tr_sort", $i + 1), "tr_sort = " . (100 + $planTour[$i]));
    }
    return true;
  endif;
  return false;
}

function xmlRequest($host, $port, $service, $xmlReq)
{
  $fp = fsockopen($host, $port, $errno, $errstr, 30);

  if (!$fp) {
    echo "$errstr ($errno)<br />\n";
  } else {
    fputs ($fp, "POST $service HTTP/1.1\r\n");
    fputs ($fp, "Host: localhost\r\n");
    fputs ($fp, "Content-type: text/xml; charset=utf-8\r\n");
    fputs ($fp, "Content-length: " . strlen($xmlReq) . "\r\n");
    fputs ($fp, "Connection: close\r\n\r\n");
    fputs ($fp, $xmlReq);
    $line = "";
    while (substr($line, 0, 14) != "<soap:Envelope" && !feof($fp)) {
      $line = fgets($fp, 128);
    }
    $in = $line;
    while (!feof($fp)) {
      $in .= fgets($fp, 128);
    }
    fclose($fp);

writeLog_xServer($in);
    $p = xml_parser_create();
    xml_parse_into_struct($p, $in, $vals, $index);
    xml_parser_free($p);
    
    return (array($vals, $index));
  }
}

function check_xml_chars($xml_str) {
  $xml_str = str_replace("&", "&amp;", $xml_str);
//  $xml_str = str_replace("'", "&apos;", $xml_str);
  $xml_str = str_replace("<", "&lt;", $xml_str);
  $xml_str = str_replace(">", "&gt;", $xml_str);
  $xml_str = str_replace('"', "&quot;", $xml_str);
  return $xml_str;
}
*/

function exec_query_db_plan($sqlquery) {
  global $db_plan;
//  echo $sqlquery . "\n";
  $res = $db_plan->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
}

function writeLog_xServer($log_text) {
  global $ajaxReqKmPrice;

  if (isset($ajaxReqKmPrice) && $ajaxReqKmPrice) {
    writeLog_ajaxReqKmPrice($log_text);
    return;
}
  
  $today = getdate();
//  echo $log_text . "\n";
  $fileHandle = @fopen("../log/xServer_" . $today['year'] . sprintf("%02d", $today['mon']) . ".log", 'a');
  if (!$fileHandle) {
    $fileHandle = @fopen("../log/xServer_" . $today['year'] . sprintf("%02d", $today['mon']) . ".web.log", 'a');
  }
  fwrite($fileHandle, "[" . date("Y-m-d H:i:s") . "] " . $log_text .  "\n");
  @fclose($fileHandle);
  return;
}

function writeLog_xTsp($log_text) {
  $today = getdate();
//  echo $log_text . "\n";
  $fileHandle = @fopen("../log/xTsp_" . $today['year'] . sprintf("%02d", $today['mon']) . ".log", 'a');
  @fwrite($fileHandle, "[" . date("Y-m-d H:i:s") . "] " . $log_text .  "\n");
  @fclose($fileHandle);
  return;
}

function check_address_google($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $update_ad_id) {
  $db_geo = get_db_geo();
  global $phpVersion;

//writeLog_xServer("check_address_google: \$update_ad_id = " . $update_ad_id);

//  $error_codes = array(
//    "200" => "G_GEO_SUCCESS",	            //Keine Fehler aufgetreten; die Adresse wurde erfolgreich analysiert. Der Geocode wurde zurückgegeben.
//    "500" => "G_GEO_SERVER_ERROR",        //Eine Geocodierungs- oder Routenanforderung konnte nicht erfolgreich verarbeitet werden, da der genaue Grund für den Fehler nicht bekannt ist.
//    "601" => "G_GEO_MISSING_QUERY",	      //Für den HTTP Parameter q wurde eine leere Adresse angegeben.
//    "602" => "G_GEO_UNKNOWN_ADDRESS",	    //Zu der angegebenen Adresse wurde keine geografische Entsprechung gefunden. Möglicherweise ist die Adresse relativ neu oder nicht richtig.
//    "603" => "G_GEO_UNAVAILABLE_ADDRESS",	//Der Geocode für die angegebene Adresse oder die Route für die angegebene Richtungsanfrage kann aus rechtlichen oder Vertragsgründen nicht zurückgegeben werden.
//    "610" => "G_GEO_BAD_KEY",	            //Der angegebene Schlüssel ist entweder ungültig oder passt nicht zur Domain, für die er angegeben wurde.
//    "620" => "G_GEO_TOO_MANY_QUERIES"	    //Der angegebene Schlüssel hat innerhalb des Zeitraums von 24 Stunden das Limit für Anforderungen überschritten oder zu viele Anforderungen in einem zu kurzen Zeitraum übermittelt. Wenn Sie zahlreiche Anforderungen gleichzeitig oder kurz hintereinander übermitteln, verwenden Sie in Ihrem Code einen Timer oder eine Pause, damit die Anforderungen nicht zu schnell übermittelt werden. 
//  );

//  $accuracy = array(
//    '0' => 'Genauigkeit unbekannt',
//    '1' => 'Land',
//    '2' => 'Region (Bundesland/Bundesstaat, Provinz, Präfektur usw. )',
//    '3' => 'Kreis (Bezirk, Gemeinde usw. )',
//    '4' => 'Ortschaft (Stadt, Dorf)',
//    '5' => 'Postleitzahl (PLZ)',
//    '6' => 'Straße',
//    '7' => 'Kreuzung',
//    '8' => 'Adresse',
//    '9' => 'Grundstück (Name des Gebäude/Grundstücks, Einkaufszentrum usw.)'
//  );

  $cnt = 1;
  $sqlquery = "SELECT adg_hsno FROM address_geo WHERE ad_id = 0 AND LEFT(adg_modify, 10) = '" . date("Y-m-d") . "'";
  $res = $db_geo->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  if($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    $cnt = $row["adg_hsno"] + 1;
  else:
    $sqlquery = "INSERT INTO address_geo (ad_id, adg_hsno, adg_accuracy, adg_gps_long, adg_gps_lat) VALUES (0, 0, 0, 0.0, 0.0)";
    $res1 = $db_geo->query($sqlquery);
    if (DB::isError($res1)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  endif;
  $res->free();

//  $sqlquery = "SELECT COUNT(*) FROM address_geo WHERE adg_modify > '" . date("Y-m-d") . " 00:00:00'";
//  $res = mysql_query($sqlquery, $mysql_db_geo) or die ($sqlquery . ": " . mysql_error($mysql_db_geo));
//  $row = mysql_fetch_row($res);
//  mysql_free_result($res);
//  $cnt = $row[0];

  $ret_val = 0;
  if ($cnt <= 10000):
    $maxTries = 30;
    $retryCnt = $maxTries;
    do {
//      $lines = file ("http://maps.google.com/maps/geo?q=" . rawurlencode(utf8_encode($ad_street) . " " . $tr_hsno) . "," . rawurlencode($ad_zipcode . " " . utf8_encode($ad_city)) . "&output=csv");
//      $result = explode(",", $lines[0]);
      $lines = check_address_google_new($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno);
      $result = explode(",", $lines);
      $result[2] = round($result[2], 6);
      $result[3] = round($result[3], 6);

      if ($result[0] == "200"):
        $ad_id = "";
        if ($update_ad_id > 0):
          $ad_id = $update_ad_id;
        else:
          $sqlquery = "SELECT ad_id FROM address WHERE ad_street = '" . my_str_check($ad_street) . "' AND ad_zipcode = '" . my_str_check($ad_zipcode) . "' AND ad_city = '" . my_str_check($ad_city) . "' AND ad_country = '$ad_country'";
//echo $sqlquery . "\n<br>";die();
          $res = $db_geo->query($sqlquery);
          if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
          if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
            $ad_id = $row["ad_id"];
          endif;
          $res->free();
          if ($ad_id == ""):
            $sqlquery = "INSERT INTO address (ad_street, ad_zipcode, ad_city, ad_country) VALUES ('" . my_str_check($ad_street) . "', '" . my_str_check($ad_zipcode) . "', '" . my_str_check($ad_city) . "', '$ad_country')";
            writeLog_xServer("check_address_google: [" . $sqlquery . "] ");
            $res = $db_geo->query($sqlquery);
            if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
            $ad_id = $db_geo->getOne("SELECT LAST_INSERT_ID()");
          endif;
        endif;
        $ret_val = $result[1];
        if ($update_ad_id > 0):
          $sqlquery = "UPDATE address_geo SET adg_accuracy = " . $result[1] . ", adg_gps_long = " . $result[3] . ", adg_gps_lat = " . $result[2] . ", adg_modify = NOW() WHERE ad_id = " . $update_ad_id . " AND adg_hsno = '" . $tr_hsno . "'";
        else:
          $sqlquery = "INSERT INTO address_geo (ad_id, adg_hsno, adg_accuracy, adg_gps_long, adg_gps_lat) VALUES ('$ad_id', '$tr_hsno', " . $result[1] . ", '" . $result[3] . "', '" . $result[2] . "')";
        endif;
        writeLog_xServer("check_address_google: [" . $sqlquery . "] ");
        $res = $db_geo->query($sqlquery);
        if (DB::isError($res)): reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage()); endif;
      elseif ($result[0] == "620" && $retryCnt > 1):
        writeLog_xServer("check_address_google: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . utf8_encode($ad_city) . "' '" . utf8_encode($ad_street) . "' '" . utf8_encode($tr_hsno) . "'] " .
          "[doing " . ($maxTries - $retryCnt + 1) . ". retry]");
        usleep(250000); // 0,25 sec schlafen
      endif;
      $sqlquery = "UPDATE address_geo SET adg_hsno = '$cnt' WHERE ad_id = 0 AND LEFT(adg_modify, 10) = '" . date("Y-m-d") . "'";
      $res = $db_geo->query($sqlquery);
      if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
//      writeLog_xServer("check_address_google: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . utf8_encode($ad_city) . "' '" . utf8_encode($ad_street) . "' '" . utf8_encode($tr_hsno) . "'] " .
//        "[" . $lines . "] [" . "http://maps.google.com/maps/geo?q=" . rawurlencode(utf8_encode($ad_street) . " " . $tr_hsno) . "," . rawurlencode($ad_zipcode . " " . utf8_encode($ad_city)) . "&output=csv" . "]");
    } while ($result[0] == "620" && --$retryCnt > 0);
    if ($result[0] == "620"):
      writeLog_xServer("check_address_google: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . utf8_encode($ad_city) . "' '" . utf8_encode($ad_street) . "' '" . utf8_encode($tr_hsno) . "'] " .
        "[giving up after " . $maxTries . " tries]");
    elseif ($retryCnt + 1 < $maxTries):
      writeLog_xServer("check_address_google: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . utf8_encode($ad_city) . "' '" . utf8_encode($ad_street) . "' '" . utf8_encode($tr_hsno) . "'] " .
        "[needed " . ($maxTries - $retryCnt + 1) . " tries]");
    endif;
  else:
    writeLog_xServer("check_address_google: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . utf8_encode($ad_city) . "' '" . utf8_encode($ad_street) . "' '" . utf8_encode($tr_hsno) . "'] " .
      "[Tageslimit erreicht]");
  endif;

  return (array($result[1], $result[2], $result[3]));
}

// Für BWV eingeführt, $full_adress = "" für Preisrechner
function check_coords_google($gps_lat, $gps_long, $update_ad_id, $full_adress = "") {
  global $phpVersion;
  $db_geo = get_db_geo();

  $lines = check_address_google_new("", "", "", "", "", $gps_lat, $gps_long, $full_adress);
  $result = explode(",", $lines);
  
  if ($result[0] == "200"):
    $ad_id = 0;
    if ($update_ad_id > 0):
      $ad_id = $update_ad_id;
    else:
      $sqlquery = "SELECT ad_id FROM address WHERE ad_street = '" . my_str_check($result[5]) . "' AND ad_zipcode = '" . my_str_check($result[3]) . "' AND ad_city = '" . my_str_check($result[4]) . "' AND ad_country = '" . $result[2] . "'";
      $res = $db_geo->query($sqlquery);
      if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
      if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
        $ad_id = $row["ad_id"];
      endif;
      $res->free();
      if ($ad_id == 0):
        $sqlquery = "INSERT INTO address (ad_street, ad_zipcode, ad_city, ad_country) VALUES ('" . my_str_check($result[5]) . "', '" . my_str_check($result[3]) . "', '" . my_str_check($result[4]) . "', '" . $result[2] . "')";
        writeLog_xServer("check_coords_google: [" . $sqlquery . "] ");
        $res = $db_geo->query($sqlquery);
        if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
        $ad_id = $db_geo->getOne("SELECT LAST_INSERT_ID()");
      endif;
    endif;
    if ($update_ad_id > 0):
      $sqlquery = "UPDATE address_geo SET adg_accuracy = " . $result[1] . ", adg_hsno = '" . $result[6] . "', adg_modify = NOW() WHERE ad_id = " . $update_ad_id . " AND adg_gps_long = " . $gps_long . " AND adg_gps_lat = " . $gps_lat;
    else:
      $sqlquery = "INSERT INTO address_geo (ad_id, adg_hsno, adg_accuracy, adg_gps_long, adg_gps_lat) VALUES ($ad_id, '" . $result[6] . "', " . $result[1] . ", " . $gps_long . ", " . $gps_lat  . ")";
    endif;
    writeLog_xServer("check_coords_google: [" . $sqlquery . "] ");
    $res = $db_geo->query($sqlquery);
    if (DB::isError($res)): reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage()); endif;
  else:
    writeLog_xServer("check_coords_google: [" . $gps_lat . ", " . $gps_long . "] failed");
  endif;

  return (array($result[0], $result[1], $result[2], $result[3], $result[4], $result[5], $result[6]));
}

// $gps_lat = 0, $gps_long = 0 für BWV eingeführt, $full_address = "" für Preisrechner
function check_address_google_new($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $gps_lat = 0, $gps_long = 0, $full_address = "") {

  $country_codes = get_country_codes();

writeLog_xServer("check_address_google_new: $ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $gps_lat, $gps_long, $full_address");

  if ($gps_lat == 0 && $gps_long == 0 && $full_address == "") {
    $ad_country_ori = $ad_country;
    if (in_array($ad_country, array_keys($country_codes)))
      $ad_country = rawurlencode(utf8_encode($country_codes[$ad_country]));
  }
  
  usleep(140000); // 0,14 sec schlafen wegen "status" => "OVER_QUERY_LIMIT"

  if ($gps_lat == 0 && $gps_long == 0) {
    if ($full_address == "") {
      $request = "https://maps.googleapis.com/maps/api/geocode/json?address=" . rawurlencode(utf8_encode($ad_street) . " " . $tr_hsno) . "," . rawurlencode($ad_zipcode . " " . utf8_encode($ad_city)) . "," . $ad_country . "&sensor=false&key=" . get_api_key();
//    $request = "https://maps.googleapis.com/maps/api/geocode/json?address=" . rawurlencode($ad_street . " " . $tr_hsno) . "," . rawurlencode($ad_zipcode . " " . $ad_city) . "," . $ad_country . "&sensor=false&key=" . get_api_key();
    } else {
      $request = "https://maps.googleapis.com/maps/api/geocode/json?address=" . rawurlencode(utf8_encode($full_address)) . "&sensor=false&key=" . get_api_key();
    }
  } else {
    $request = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" . $gps_lat . "," . $gps_long . "&result_type=country|administrative_area_level_1|administrative_area_level_2|locality|postal_code|route|intersection|street_address|premise|subpremise&key=" . get_api_key();
//https://maps.googleapis.com/maps/api/geocode/json?latlng=52.542843,12.979395&result_type=country|administrative_area_level_1|administrative_area_level_2|locality|postal_code|route|intersection|street_address|premise|subpremise&key=AIzaSyA9a06yPWYsH90pQA55KBz3JFf-MfVbYBg
//https://maps.googleapis.com/maps/api/geocode/json?latlng=53.9199,9.9386&result_type=country|administrative_area_level_1|administrative_area_level_2|locality|postal_code|route|intersection|street_address|premise|subpremise&key=AIzaSyA9a06yPWYsH90pQA55KBz3JFf-MfVbYBg
//https://maps.googleapis.com/maps/api/geocode/json?address=Demexallee 1,14641 Wustermark,DE&sensor=false&key=AIzaSyA9a06yPWYsH90pQA55KBz3JFf-MfVbYBg
  }

  if ($gps_lat == 0 && $gps_long == 0 && $full_address == "")
    $request = strtr($request, array("%C3%A4" => "ae", "%C3%B6" => "oe", "%C3%BC" => "ue", "%C3%84" => "Ae", "%C3%96" => "Oe", "%C3%9C" => "Ue", "%C3%9F" => "ss"));
  $json_answer = file_get_contents($request);

  $json_answerA = json_decode($json_answer, true);
  $ret_code = 602;
  $adg_accuracy = 0;
  $lat = 0;
  $long = 0;
  if ($json_answerA["status"] == "OK"):
    $ret_code = 200;
    if ($gps_lat == 0 && $gps_long == 0 && $full_address == "") {
      $lat = round($json_answerA["results"][0]["geometry"]["location"]["lat"], 6);
      $long = round($json_answerA["results"][0]["geometry"]["location"]["lng"], 6);
    } else {
      $street_number = "";
      $route =  "";
      $locality =  "";
      $country =  "";
      $postal_code =  "";
      foreach($json_answerA["results"][0]["address_components"] as $address_component) {
        switch ($address_component["types"][0]):
          case "street_number":
            $street_number = $address_component["short_name"]; break;
          case "route":
            $route = $address_component["short_name"]; break;
          case "locality":
            $locality = $address_component["short_name"]; break;
          case "country":
            $country = $address_component["short_name"]; break;
          case "postal_code":
            $postal_code = $address_component["short_name"]; break;
        endswitch;
	  }
    }
    if (count($json_answerA["results"]) > 0 && count($json_answerA["results"][0]["types"]) > 0):
      foreach ($json_answerA["results"][0]["types"] as $type) {
		$type_accuracy = 0;
        switch ($type):
          case "country":
            $type_accuracy = 1; break;
          case "administrative_area_level_1":
            $type_accuracy = 2; break;
          case "administrative_area_level_2":
            $type_accuracy = 3; break;
          case "locality":
            $type_accuracy = 4; break;
          case "postal_code":
            $type_accuracy = 5; break;
          case "route":
            $type_accuracy = 6; break;
          case "intersection":
            $type_accuracy = 7; break;
          case "street_address":
            $type_accuracy = 8; break;
          case "establishment":
          case "premise":
          case "subpremise":
            $type_accuracy = 9;
        endswitch;
        if ($type_accuracy > $adg_accuracy)
          $adg_accuracy = $type_accuracy;
      }
    endif;
    if ($adg_accuracy == 0 && $json_answerA["results"][0]["geometry"]["location_type"] == "ROOFTOP"):
      $adg_accuracy = 8;
//    elseif ($adg_accuracy == 0):
//      $ret_code = 602;
    endif;
  elseif ($json_answerA["status"] == "OVER_QUERY_LIMIT"):
    $ret_code = 620;
  endif;

  if ($gps_lat == 0 && $gps_long == 0 && $full_address == "") {
    writeLog_xServer("check_address_google_new: ['" . $ad_country_ori . "' '" . $ad_zipcode . "' '" . $ad_city . "' '" . $ad_street . "' '" . $tr_hsno . "'] " .
      "[" . "$ret_code, $adg_accuracy, $lat, $long" . "] [" . $request . "]\n" . $json_answer);
    return "$ret_code,$adg_accuracy,$lat,$long";
  } else {
    writeLog_xServer("check_address_google_new: [" . $gps_lat . "," . $gps_long . "] " .
      "[" . $ret_code . ", " . $adg_accuracy . ", " . utf8_decode($country) . ", " . utf8_decode($postal_code) . ", " . utf8_decode($locality) . ", " . utf8_decode($route) . ", " . utf8_decode($street_number) . "] [" . $request . "]\n" . $json_answer);
    return $ret_code . "," . $adg_accuracy . "," . utf8_decode($country) . "," . utf8_decode($postal_code) . "," . utf8_decode($locality) . "," . utf8_decode($route) . "," . utf8_decode($street_number);
  }
}

function get_distances_from_db($origins, $destinations) {
  $db_geo = get_db_geo();
  global $phpVersion;

  $originsArr = explode("|", $origins);
  $destinationsArr = explode("|", $destinations);

  $retArr = array();
  for ($i = 0; $i < count($originsArr); $i++):
    $tmp_pair1 = explode(",", $originsArr[$i]);
    for ($j = 0; $j < count($destinationsArr); $j++):
      $tmp_pair2 = explode(",", $destinationsArr[$j]);
      if ($tmp_pair1[1] != $tmp_pair2[1] || $tmp_pair1[0] != $tmp_pair2[0]):
        $sqlquery = "SELECT di_distance, di_duration, di_status FROM address_geo.distance WHERE " .
          "di_lat_start = " . $tmp_pair1[0] . " AND " .
          "di_long_start = " . $tmp_pair1[1] . " AND " .
          "di_lat_dest = " . $tmp_pair2[0] . " AND " .
          "di_long_dest = " . $tmp_pair2[1];
writeLog_xServer("get_distances_from_db: [" . $sqlquery . "]");
        $res = $db_geo->query($sqlquery);
        if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
        if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
          $retArr[] = array($tmp_pair1[1], $tmp_pair1[0], $tmp_pair2[1], $tmp_pair2[0], $row["di_distance"], $row["di_duration"], $row["di_status"]);
        else:
          $tmpArr = get_distances_google($tmp_pair1[0] . "," . $tmp_pair1[1] . "|" . $tmp_pair2[0] . "," . $tmp_pair2[1], $tmp_pair1[0] . "," . $tmp_pair1[1] . "|" . $tmp_pair2[0] . "," . $tmp_pair2[1]);
          $retArr[] = $tmpArr[1];
        endif;
      else:
        $retArr[] = array($tmp_pair1[1], $tmp_pair1[0], $tmp_pair2[1], $tmp_pair2[0], 0, 0, 0);
      endif;
    endfor;
  endfor;
  return $retArr;
}

function get_distances_google($origins, $destinations) {
  $db_geo = get_db_geo();
  global $phpVersion;

  $request = "https://maps.googleapis.com/maps/api/distancematrix/json?origins=$origins&destinations=$destinations&mode=driving&language=de-DE&sensor=false&key=" . get_api_key();

  $maxTries = 30;
  $retryCnt = $maxTries;
  do {
    $json_answer = file_get_contents($request);
    $json_answerA = json_decode($json_answer, true);
    writeLog_xServer("get_distances_google: [" . $request . "]\n" . $json_answer, "");
    
    $retArr = array();
    if ($json_answerA["status"] == "OK"):
      $tmp_origins = explode("|", $origins);
      $tmp_destinations = explode("|", $destinations);  
    
      for ($i = 0; $i < count($json_answerA["origin_addresses"]); $i++):
        $tmp_pair1 = explode(",", $tmp_origins[$i]);
        for ($j = 0; $j < count($json_answerA["destination_addresses"]); $j++):
          $tmp_pair2 = explode(",", $tmp_destinations[$j]);
          $sqlquery = "SELECT di_id FROM address_geo.distance WHERE " .
            "di_long_start = " . $tmp_pair1[1] . " AND " .
            "di_lat_start = " . $tmp_pair1[0] . " AND " .
            "di_long_dest = " . $tmp_pair2[1] . " AND " .
            "di_lat_dest = " . $tmp_pair2[0];
          $res = $db_geo->query($sqlquery);
          if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
          if (!($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow()))):
            if ($json_answerA["rows"][$i]["elements"][$j]["distance"]["value"] != 0):
              $sqlquery = 
                "INSERT INTO address_geo.distance (di_long_start, di_lat_start, di_long_dest, di_lat_dest, di_distance, di_duration, di_status) VALUES(" .
                  $tmp_pair1[1] . ", " . $tmp_pair1[0] . ", " . $tmp_pair2[1] . ", " . $tmp_pair2[0] . ", " .
                  $json_answerA["rows"][$i]["elements"][$j]["distance"]["value"] . ", " . 
                  $json_answerA["rows"][$i]["elements"][$j]["duration"]["value"] . ", '" . 
                  $json_answerA["rows"][$i]["elements"][$j]["status"] . "')";
              writeLog_xServer("get_distances_google: [" . $sqlquery . "]");
              $res = $db_geo->query($sqlquery);
              if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
            endif;
          else:
            $res->free();
          endif;
          $retArr[] = array(
            $tmp_pair1[1], $tmp_pair1[0], $tmp_pair2[1], $tmp_pair2[0], 
            $json_answerA["rows"][$i]["elements"][$j]["distance"]["value"],
            $json_answerA["rows"][$i]["elements"][$j]["duration"]["value"],
            $json_answerA["rows"][$i]["elements"][$j]["status"]);        
        endfor;
      endfor;
    elseif ($retryCnt > 1):
      writeLog_xServer("get_distances_google: ['" . $origins . "', '" . $destinations . "'] " .
        "[doing " . ($maxTries - $retryCnt + 1) . ". retry]");
      usleep(250000); // 0,25 sec schlafen
    endif;
  } while ($json_answerA["status"] != "OK" && --$retryCnt > 0);
  if ($json_answerA["status"] != "OK"):
    writeLog_xServer("get_distances_google: ['" . $origins . "', '" . $destinations . "'] " .
      "[giving up after " . $maxTries . " tries]");
  elseif ($retryCnt + 1 < $maxTries):
    writeLog_xServer("get_distances_google: ['" . $origins . "', '" . $destinations . "'] " .
      "[needed " . ($maxTries - $retryCnt + 1) . " tries]");
  endif;
  writeLog_xServer("get_distances_google: \$retArr = " . json_encode($retArr));
  return $retArr;
}

function check_expired($modify_date, $force_date = '0000-00-00 00:00:00') {
  if ($force_date == '0000-00-00 00:00:00')
    return (time() < strtotime($modify_date) + getParameterValue("0", "GEOCACHE_LIFETIME", "0") * 24 * 3600);
  writeLog_xServer("check_expired: \$force_date = '" . $force_date . "', \$modify_date = '" . $modify_date . "'");
  return (strtotime($force_date) < strtotime($modify_date));
}

function get_coords($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $zipcode_only = false) {
  $db_geo = get_db_geo();
  global $phpVersion;

  $retArr = array(0, 0);
  $ad_country_clause = "ad.ad_country = '" . $ad_country . "'";
  if ($ad_country == "DE" || $ad_country == "D")
    $ad_country_clause = "(ad.ad_country = 'DE' OR ad.ad_country = 'D')";
  $sqlquery = "SELECT adg.ad_id, adg_gps_long, adg_gps_lat, adg_accuracy, adg_modify FROM address_geo.address AS ad, address_geo.address_geo AS adg WHERE " .
    $ad_country_clause . " AND ad.ad_zipcode = '" . my_str_check($ad_zipcode) . "' AND ad.ad_city = '" . my_str_check($ad_city) . "' AND ad.ad_street = '" . my_str_check($ad_street) . "' " . 
    "AND ad.ad_id = adg.ad_id AND adg.adg_hsno = '" . $tr_hsno . "'";
writeLog_xServer("get_coords: [" . $sqlquery . "]");
  $res = $db_geo->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  $is_valid = false;
  $update_ad_id = 0;
  if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
writeLog_xServer("get_coords: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . $ad_city . "' '" . $ad_street . "' '" . $tr_hsno . "'] " .
  "[" . $row["adg_accuracy"] . ", " . $row["adg_gps_lat"] . ", " . $row["adg_gps_long"] . ", " . $row["adg_modify"] . "]");
    $is_valid = check_expired($row["adg_modify"]);
    if (!$is_valid)
      $update_ad_id = $row["ad_id"];
  endif;
  if ($is_valid) {
    if ($row["adg_accuracy"] >= 6 || ($zipcode_only && $row["adg_accuracy"] == 5)) {
      $retArr = array($row["adg_gps_lat"], $row["adg_gps_long"]);
    }
  } else {
    // Warte 0,75 Zehntel-Sekunden um "620 - too man queries" zu vermeiden
    usleep(75000);
    $row_google = check_address_google(($ad_country == "D" ? "DE" : $ad_country), $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $update_ad_id);    
    if ($row_google[0] >= 6) {
      $retArr = array($row_google[1], $row_google[2]);
    }
  }
  $res->free();
  return($retArr);
}

function set_coords($ad_country, $ad_zipcode, $ad_city, $ad_street, $tr_hsno, $adg_gps_lat, $adg_gps_long, $adg_gps_accuracy) {
  $db_geo = get_db_geo();
  global $phpVersion;

  $adg_gps_lat = round($adg_gps_lat, 6);
  $adg_gps_lomg = round($adg_gps_long, 6);
  $sqlquery = "SELECT adg.ad_id, adg_gps_long, adg_gps_lat, adg_accuracy, adg_modify FROM address_geo.address AS ad, address_geo.address_geo AS adg WHERE " .
    "ad.ad_country = '" . $ad_country . "' AND ad.ad_zipcode = '" . my_str_check($ad_zipcode) . "' AND ad.ad_city = '" . my_str_check($ad_city) . "' AND ad.ad_street = '" . my_str_check($ad_street) . "' " . 
    "AND ad.ad_id = adg.ad_id AND adg.adg_hsno = '" . $tr_hsno . "'";
writeLog_xServer("set_coords: [" . $sqlquery . "]");
  $res = $db_geo->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  $update_ad_id = 0;
  if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
writeLog_xServer("set_coords: ['" . $ad_country . "' '" . $ad_zipcode . "' '" . $ad_city . "' '" . $ad_street . "' '" . $tr_hsno . "'] [" . $row["adg_accuracy"] . ", " . $row["adg_gps_lat"] . ", " . $row["adg_gps_long"] . ", " . $row["adg_modify"] . "]");
    $update_ad_id = $row["ad_id"];
  else:
    $sqlquery = "SELECT ad_id FROM address WHERE ad_street = '" . my_str_check($ad_street) . "' AND ad_zipcode = '" . my_str_check($ad_zipcode) . "' AND ad_city = '" . my_str_check($ad_city) . "' AND ad_country = '" . $ad_country . "'";
    $res1 = $db_geo->query($sqlquery);
    if (DB::isError($res1)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res1->getMessage());
    if ($row1 = ($phpVersion >= "8.0" ? $res1->fetch_assoc() : $res1->fetchRow())):
      $ad_id = $row1["ad_id"];
    else:
      $sqlquery = "INSERT INTO address (ad_street, ad_zipcode, ad_city, ad_country) VALUES ('" . my_str_check($ad_street) . "', '" . my_str_check($ad_zipcode) . "', '" . my_str_check($ad_city) . "', '$ad_country')";
writeLog_xServer("set_coords: [" . $sqlquery . "] ");
      $res2 = $db_geo->query($sqlquery);
      if (DB::isError($res2)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res2->getMessage());
      $ad_id = $db_geo->getOne("SELECT LAST_INSERT_ID()");
    endif;
  endif;
  $res->free();

  if ($update_ad_id > 0):
    $sqlquery = "UPDATE address_geo SET adg_accuracy = " . $adg_gps_accuracy . ", adg_gps_lat = " . $adg_gps_lat . ", adg_gps_long = " . $adg_gps_long . ", adg_modify = NOW() WHERE ad_id = " . $update_ad_id . " AND adg_hsno = '" . $tr_hsno . "'";
  else:
    $sqlquery = "INSERT INTO address_geo (ad_id, adg_hsno, adg_accuracy, adg_gps_long, adg_gps_lat) VALUES ('$ad_id', '$tr_hsno', " . $adg_gps_accuracy . ", '" . $adg_gps_long . "', '" . $adg_gps_lat . "')";
  endif;
  writeLog_xServer("set_coords: [" . $sqlquery . "] ");
  $res = $db_geo->query($sqlquery);
  if (DB::isError($res)): reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage()); endif;
}

// Für BWV eingeführt, $full_address = "" für Preisrechner
function get_address($gps_lat, $gps_long, $full_address = "") {
  $db_geo = get_db_geo();
  global $phpVersion;

  $retArr = array("", "", "", "", "");
  $is_valid = false;
  $update_ad_id = 0;
  if ($full_address == "") {
    $sqlquery = "SELECT ad.ad_id, ad.ad_country, ad.ad_zipcode, ad.ad_city, ad.ad_street, adg.adg_hsno, adg.adg_accuracy, adg.adg_modify FROM address_geo.address AS ad, address_geo.address_geo AS adg WHERE " .
      "adg.adg_gps_long = " . $gps_long . " AND adg_gps_lat = " . $gps_lat . " AND ad.ad_id = adg.ad_id";
writeLog_xServer("get_address: [" . $sqlquery . "]");
    $res = $db_geo->query($sqlquery);
    if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
    if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
writeLog_xServer("get_address: ['" . $row["ad_country"] . "' '" . $row["ad_zipcode"] . "' '" . $row["ad_city"] . "' '" . $row["ad_street"] . "' '" . $row["adg_hsno"] . "'] " .
  "[" . $row["adg_accuracy"] . ", " . $gps_lat . ", " . $gps_long . ", " . $row["adg_modify"] . "]");
      $is_valid = check_expired($row["adg_modify"]);
      if (!$is_valid)
        $update_ad_id = $row["ad_id"];
    endif;
    $res->free();
  }
  if ($is_valid) {
    $retArr = array($row["adg_accuracy"], $row["ad_country"], $row["ad_zipcode"], $row["ad_city"], $row["ad_street"], $row["adg_hsno"]);
  } else {
    // Warte 0,75 Zehntel-Sekunden um "620 - too man queries" zu vermeiden
    usleep(75000);
    $row_google = check_coords_google($gps_lat, $gps_long, $update_ad_id, $full_address);    
    $retArr = array($row_google[1], $row_google[2], $row_google[3], $row_google[4], $row_google[5], $row_google[6]);
  }
  return($retArr);
}

// $duration_only = false für BWV eingeführt
function get_distance_google($origin, $destination, $force_date = '0000-00-00 00:00:00', $mode, $duration_only = false) {
  $db_geo = get_db_geo();
  global $phpVersion;

  $is_valid = false;
  $update_di_id = 0;
  if ($mode == "km") {
    $sqlquery = "SELECT di_id, di_distance, di_duration, di_status, di_modify FROM address_geo.distance WHERE " .
      "di_lat_start = " . $origin[0] . " AND " .
      "di_long_start = " . $origin[1] . " AND " .
      "di_lat_dest = " . $destination[0] . " AND " .
      "di_long_dest = " . $destination[1];
    writeLog_xServer("get_distance_google: [" . $sqlquery . "]");
    $res = $db_geo->query($sqlquery);
    if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
    if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
      writeLog_xServer("get_distance_google: [(" . $origin[0] . ", " . $origin[1] . ") -> (" . $destination[0] . ", " . $destination[1] . ") " .
        "[" . $row["di_distance"] . ", " . $row["di_duration"] . ", " . $row["di_status"] . ", " . $row["di_modify"] . "]");
      $is_valid = check_expired($row["di_modify"], $force_date);
      if (!$is_valid)
        $update_di_id = $row["di_id"];
    endif;
    $res->free();
  }
//  writeLog_xServer("get_distance_google: \$is_valid = " . $is_valid . ", \$update_di_id = " . $update_di_id);
  if ($is_valid):
    $retVal = $row["di_distance"];
    if ($duration_only):
      $retVal = $row["di_duration"];
    endif;
  else:
    $request = "https://maps.googleapis.com/maps/api/distancematrix/json?origins=" . $origin[0] . "," . $origin[1] . "&destinations=" . $destination[0] . "," . $destination[1] . "&mode=driving" . ($mode == "traffic" ? "&departure_time=now" : "") . "&language=de-DE&sensor=false&key=" . get_api_key();
    $retVal = 0;
    $maxTries = 5;
    $retryCnt = $maxTries;
    do {
      $json_answer = file_get_contents($request);
      $json_answerA = json_decode($json_answer, true);
      writeLog_xServer("get_distance_google: [" . $request . "]\n" . $json_answer, "");
      if ($json_answerA["status"] == "OK"):
        if ($json_answerA["rows"][0]["elements"][0]["distance"]["value"] != 0 && $mode == "km"):
          $retVal = $json_answerA["rows"][0]["elements"][0]["distance"]["value"];
          if ($duration_only):
            $retVal = $json_answerA["rows"][0]["elements"][0]["duration"]["value"];
          endif;
          writeLog_xServer("get_distance_google: \$retArr = " . json_encode(array(
            $origin[0], $origin[1], $destination[0], $destination[1], 
            $json_answerA["rows"][0]["elements"][0]["distance"]["value"],
            $json_answerA["rows"][0]["elements"][0]["duration"]["value"],
            $json_answerA["rows"][0]["elements"][0]["status"])
          ));
          if ($update_di_id > 0):
            $sqlquery = 
              "UPDATE address_geo.distance SET di_lat_start = " . $origin[0] . ", di_long_start = " . $origin[1] .
                ", di_lat_dest = " . $destination[0] . ", di_long_dest = " . $destination[1] .
                ", di_distance = " . $json_answerA["rows"][0]["elements"][0]["distance"]["value"] .
                ", di_duration = " . $json_answerA["rows"][0]["elements"][0]["duration"]["value"] .
                ", di_status = '" . $json_answerA["rows"][0]["elements"][0]["status"] . "'" .
                ", di_modify = NOW()" .
                " WHERE di_id = " . $update_di_id;
          else:
            $sqlquery = 
              "INSERT INTO address_geo.distance (di_lat_start, di_long_start, di_lat_dest, di_long_dest, di_distance, di_duration, di_status) VALUES(" .
                $origin[0] . ", " . $origin[1] . ", " . $destination[0] . ", " . $destination[1] . ", " .
                $json_answerA["rows"][0]["elements"][0]["distance"]["value"] . ", " . 
                $json_answerA["rows"][0]["elements"][0]["duration"]["value"] . ", '" . 
                $json_answerA["rows"][0]["elements"][0]["status"] . "')";
          endif;
          writeLog_xServer("get_distance_google: [" . $sqlquery . "]");
          $res = $db_geo->query($sqlquery);
          if (DB::isError($res)): reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage()); endif;
        elseif ($json_answerA["rows"][0]["elements"][0]["duration_in_traffic"]["value"] != 0 && $mode == "traffic"):
          $retVal = $json_answerA["rows"][0]["elements"][0]["duration_in_traffic"]["value"];
          writeLog_xServer("get_distance_google: \$retArr = " . json_encode(array(
            $origin[0], $origin[1], $destination[0], $destination[1], 
            $json_answerA["rows"][0]["elements"][0]["distance"]["value"],
            $json_answerA["rows"][0]["elements"][0]["duration"]["value"],
            $json_answerA["rows"][0]["elements"][0]["duration_in_traffic"]["value"],
            $json_answerA["rows"][0]["elements"][0]["status"])
          ));
        endif;
      elseif ($retryCnt > 1):
        writeLog_xServer("get_distance_google: ['" . $origin[0] . ", " . $origin[1] . "', '" . $destination[0] . ", " . $destination[1] . "'] [\$mode = ". $mode . "]" .
          "[doing " . ($maxTries - $retryCnt + 1) . ". retry]");
        usleep(250000); // 0,25 sec schlafen
      endif;
    } while ($json_answerA["status"] != "OK" && --$retryCnt > 0);
    if ($json_answerA["status"] != "OK"):
      writeLog_xServer("get_distance_google: ['" . $origin[0] . ", " . $origin[1] . "', '" . $destination[0] . ", " . $destination[1] . "'] [\$mode = ". $mode . "]" .
        "[giving up after " . $maxTries . " tries]");
    elseif ($retryCnt + 1 < $maxTries):
      writeLog_xServer("get_distance_google: ['" . $origin[0] . ", " . $origin[1] . "', '" . $destination[0] . ", " . $destination[1] . "'] [\$mode = ". $mode . "]" .
        "[needed " . ($maxTries - $retryCnt + 1) . " tries]");
    endif;
  endif;
  return $retVal;
}

function get_distance_osm($origin, $destination) {
  $db_geo = get_db_geo();
  global $phpVersion;

  $retVal = 0;
  $sqlquery = "SELECT dio_id, dio_distance, dio_weight, dio_modify FROM address_geo.distance_osm WHERE " .
    "dio_lat_start = " . $origin[0] . " AND " .
    "dio_long_start = " . $origin[1] . " AND " .
    "dio_lat_dest = " . $destination[0] . " AND " .
    "dio_long_dest = " . $destination[1];
//  writeLog_xServer("get_distance_osm: [" . $sqlquery . "]");
  $res = $db_geo->query($sqlquery);
  if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  $is_valid = false;
  $update_dio_id = 0;
  if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    writeLog_xServer("get_distance_osm   : [(" . $origin[0] . ", " . $origin[1] . ") -> (" . $destination[0] . ", " . $destination[1] . ") " .
      "[" . $row["dio_distance"] . ", " . $row["dio_weight"] . ", " . $row["dio_modify"] . "]");
    $is_valid = check_expired($row["dio_modify"]);
    if (!$is_valid)
      $update_dio_id = $row["dio_id"];
  endif;
  $res->free();
  if ($is_valid):
    $retVal = $row["dio_distance"];
  else:
    $request = "https://routing.openstreetmap.de/routed-car/route/v1/driving/" . $origin[1] . "," . $origin[0] . ";" . $destination[1] . "," . $destination[0] . "?overview=false&alternatives=false&steps=false";
    $retVal = 0;
    $maxTries = 1;
    $retryCnt = $maxTries;
    do {
      $json_answer = file_get_contents($request);
      $json_answerA = json_decode($json_answer, true);
      writeLog_xServer("get_distance_osm: [" . $request . "]\n" . $json_answer, "");
      if ($json_answerA["code"] == "Ok"):
//      writeLog_xServer("get_distance_osm: " . $json_answerA["routes"][0]["distance"], "");
        if ($json_answerA["routes"][0]["distance"] != 0):
          $retVal = $json_answerA["routes"][0]["distance"];
          writeLog_xServer("get_distance_osm: \$retArr = " . json_encode(
            array(
              $origin[0], $origin[1], $destination[0], $destination[1], 
              $json_answerA["routes"][0]["distance"],
              $json_answerA["routes"][0]["duration"],
              $json_answerA["routes"][0]["weight"])
          ));
          if ($update_dio_id > 0):
            $sqlquery = 
              "UPDATE address_geo.distance_osm SET dio_lat_start = " . $origin[0] . ", dio_long_start = " . $origin[1] .
                ", dio_lat_dest = " . $destination[0] . ", dio_long_dest = " . $destination[1] .
                ", dio_distance = " . $json_answerA["routes"][0]["distance"] .
                ", dio_duration = " . $json_answerA["routes"][0]["duration"] .
                ", dio_weight = " . $json_answerA["routes"][0]["weight"] .
                ", dio_modify = NOW()" .
                " WHERE dio_id = " . $update_dio_id;
          else:
            $sqlquery = 
              "INSERT INTO address_geo.distance_osm (dio_lat_start, dio_long_start, dio_lat_dest, dio_long_dest, dio_distance, dio_duration, dio_weight) VALUES (" .
                $origin[0] . ", " . $origin[1] . ", " . $destination[0] . ", " . $destination[1] . ", " .
                $json_answerA["routes"][0]["distance"] . ", " . 
                $json_answerA["routes"][0]["duration"] . ", '" . 
                $json_answerA["routes"][0]["weight"] . "')";
          endif;
          writeLog_xServer("get_distance_osm: [" . $sqlquery . "]");
          $res = $db_geo->query($sqlquery);
          if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
        endif;
      elseif ($retryCnt > 1):
        writeLog_xServer("get_distance_osm: ['" . $origin . "', '" . $destination . "'] " .
          "[doing " . ($maxTries - $retryCnt + 1) . ". retry]");
        usleep(250000); // 0,25 sec schlafen
      endif;
    } while ($json_answerA["code"] != "Ok" && --$retryCnt > 0);
    if ($json_answerA["code"] != "Ok"):
      writeLog_xServer("get_distance_osm: ['" . $origin[0] . ", " . $origin[1] . "', '" . $destination[0] . ", " . $destination[1] . "'] " .
        "[giving up after " . $maxTries . " tries]");
    elseif ($retryCnt + 1 < $maxTries):
      writeLog_xServer("get_distance_osm: ['" . $origin[0] . ", " . $origin[1] . "', '" . $destination[0] . ", " . $destination[1] . "'] " .
        "[needed " . ($maxTries - $retryCnt + 1) . " tries]");
    endif;
  endif;
  return $retVal;
}

function get_tour_distance_new($stops, $force_date = '0000-00-00 00:00:00', $mode = "km") {
  global $hq_id, $phpVersion;
  $db_geo = get_db_geo();

  $retVal = 0;
  $retVal_osm = 0;

  $is_valid = false;
  $stops[0][0] = round($stops[0][0], 6);
  $stops[0][1] = round($stops[0][1], 6);
  for ($i = 0; $i < count($stops) - 1; $i++):
    $stops[$i+1][0] = round($stops[$i+1][0], 6);
    $stops[$i+1][1] = round($stops[$i+1][1], 6);
    if ($mode == "km") {
      $sqlquery = "SELECT di_id, di_distance, di_duration, di_status, di_modify FROM address_geo.distance WHERE " .
        "di_lat_start = " . $stops[$i][0] . " AND " .
        "di_long_start = " . $stops[$i][1] . " AND " .
        "di_lat_dest = " . $stops[$i+1][0] . " AND " .
        "di_long_dest = " . $stops[$i+1][1];
      $res = $db_geo->query($sqlquery);
      if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
      $is_valid = false;
      if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
        writeLog_xServer("get_tour_distance_new: [(" . $stops[$i][0] . ", " . $stops[$i][1] . ") -> (" . $stops[$i+1][0] . ", " . $stops[$i+1][1] . ") " .
          "[" . $row["di_distance"] . ", " . $row["di_duration"] . ", " . $row["di_status"] . ", " . $row["di_modify"] . "]");
//        $is_valid = check_expired($row["di_modify"], $force_date);
        $is_valid = false;
        if (!$is_valid):
          writeLog_xServer("get_tour_distance_new: [no longer valid]");
          break;
        endif;
        if ($mode == "km"):
          $retVal += $row["di_distance"];
        else:
          $retVal += $row["di_duration"];
        endif;
      else:
        writeLog_xServer("get_tour_distance_new: [(" . $stops[$i][0] . ", " . $stops[$i][1] . ") -> (" . $stops[$i+1][0] . ", " . $stops[$i+1][1] . ") " .
          "[not found]");
        break;
      endif;
      $res->free();
	}
//    if ($mode == "km")
//      $retVal_osm += get_distance_osm($stops[$i], $stops[$i + 1]);
  endfor;
  if (!$is_valid) {
    $zones = get_zone_distance($stops, 0, 0, ($mode != "km"), false, true);
    if ($mode == "km")
      $retVal = (int) str_replace(" km", "", $zones["distance"]);
    else 
      $retVal = (int) str_replace("s", "", $zones["duration"]);
  }
  if ($mode == "km") {
    writeLog_xServer("get_tour_distance_new: [" . ($retVal / 1000) . ", " . ($retVal_osm / 1000) . "] [\$mode = " . $mode . "]");
    return array($retVal / 1000, $retVal_osm / 1000);
  }
  writeLog_xServer("get_tour_distance_new: [" . $retVal . ", " . $retVal_osm . "] [\$mode = " . $mode . "]");
  return array($retVal, $retVal_osm);
}

function get_tour_distance($stops, $force_date = '0000-00-00 00:00:00', $mode = "km") {
  $db_geo = get_db_geo();

get_tour_distance_new($stops, $force_date, $mode);
  $retVal = 0;
  $retVal_osm = 0;

  $stops[0][0] = round($stops[0][0], 6);
  $stops[0][1] = round($stops[0][1], 6);
  for ($i = 0; $i < count($stops) - 1; $i++):
    $stops[$i+1][0] = round($stops[$i+1][0], 6);
    $stops[$i+1][1] = round($stops[$i+1][1], 6);
    $retVal += get_distance_google($stops[$i], $stops[$i + 1], $force_date, $mode);
//    if ($mode == "km")
//      $retVal_osm += get_distance_osm($stops[$i], $stops[$i + 1]);
  endfor;
  if ($mode == "km") {
    writeLog_xServer("get_tour_distance: [" . ($retVal / 1000) . ", " . ($retVal_osm / 1000) . "] [\$mode = " . $mode . "]");
    return array($retVal / 1000, $retVal_osm / 1000);
  }
  writeLog_xServer("get_tour_distance: [" . $retVal . ", " . $retVal_osm . "] [\$mode = " . $mode . "]");
  return array($retVal, $retVal_osm);
}

function get_zone_distance($stops, $hq_id, $jb_id = 0, $traffic = false, $optimized = false, $distanceOnly = false) {
  global $db_geo, $phpVersion, $dbhostPZM, $dbhostPZM1, $check_bwv;

  writeLog_xServer("get_zone_distance: [\$check_bwv = " . $check_bwv . "]");

  if (!isset($check_bwv))
    $check_bwv = false;

//  writeLog_xServer("get_zone_distance: [\$check_bwv = " . $check_bwv . "]");

  $stopsForPost = array();
  for ($i = 0; $i < count($stops); $i++):
    $stopsForPost[] = $stops[$i][0] . "," . $stops[$i][1];
  endfor;

  $shortest = false;
  $ridiculousShortest = false;
  $roundTripKm = false;
  if (!$traffic && !$optimized /* && !$distanceOnly */ && !$check_bwv) {
//    if ($hq_id == 4 || $hq_id == 6 || $hq_id == 8)
    if (getParameterValue("0", "PZM_ROUNDTRIPKM", $hq_id) == "1")
      $roundTripKm = true;
    if (getParameterValue("0", "PZM_SHORTEST", $hq_id) == "1")
      $shortest = true;
    if (getParameterValue("0", "PZM_SHORTEST", $hq_id) == "2")
      $ridiculousShortest = true;
  }
//  writeLog_xServer("get_zone_distance: [\$shortest = " . $shortest . "]");

  if ($check_bwv)
    $shortest = true; // zur Prüfung ob ein Auftrag ein Fern-BWV-Auftrag ist, soll "Best of Three" verwendet werden

//  writeLog_xServer("get_zone_distance: [\$shortest = " . $shortest . "]");

//  $url = "http://172.16.0.158:81/api/pzm";
//  $url = "http://Preis-Zonen-Modell:8080/api/pzm";
  $url = "http://" . $dbhostPZM . "/api/pzm";
  $postArray = array(
    "stations" => $stopsForPost,
    "hq_id" => (int) $hq_id,
    "jb_id" => (int) $jb_id,
    "traffic" => (bool) $traffic,
    "optimized" => (bool) $optimized,
    "distanceOnly" => (bool) $distanceOnly,
    "shortest" => (bool) $shortest,
    "ridiculousShortest" => (bool) $ridiculousShortest,
    "roundTripKm"=> (bool) $roundTripKm,
    "api_key" => get_api_key()
  );
  $jsonData = json_encode($postArray);

  writeLog_xServer("get_zone_distance: [" . $jsonData . "]");
  $i = 0;
  do {
    if ($i++ == 1)
      $url = "http://" . $dbhostPZM1 . "/api/pzm";
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData);

    $json_response = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
//    if ($i == 1)
//      $status = 0;

    if ($status != 200) {
      writeLog_xServer("get_zone_distance: " . "Error: call to URL " . $url . " failed with status " . $status . ", response \"" . $json_response . "\", curl_error " . curl_error($ch) . ", curl_errno " . curl_errno($ch));
//      die("Error: call to URL $url failed with status $status, response $json_response, curl_error " . curl_error($ch) . ", curl_errno " . curl_errno($ch));
      include_once("../include/email/htmlMimeMail.php");
      $mailObj = new htmlMimeMail();
      $mailObj->setFrom("auto-mailer@assecutor.de");
      $mailObj->setSubject("PZM-Server " . $url . " nicht erreichbar");
      $mailObj->setText("Error: call to URL " . $url . " failed with status " . $status . ", response \"" . $json_response . "\", curl_error " . curl_error($ch) . ", curl_errno " . curl_errno($ch));
      $mailResult = $mailObj->send(array("ca@assecutor.de"), 'smtp');
//      $mailResult = $mailObj->send(array("support@assecutor.de"), 'smtp');
    }

    curl_close($ch);
    writeLog_xServer("get_zone_distance: [" . $json_response . "] [" . $status . "]");
  } while($status != 200 && $i < 2 && $dbhostPZM1 != "");

  $responseArr = json_decode($json_response, true);

  $db_geo = get_db_geo();
//  $distance = 0;
//  $duration = 0;
  $di_ids = array("legs" => "", "eapLeg" => "", "edcLeg" => "");
  $di_modify = date("Y-m-d H:i:s");
  if (!is_null($responseArr['timestamp']))
    $di_modify = str_replace("T", " ", substr($responseArr['timestamp'], 0, 19));
  foreach (array("legs", "eapLeg", "edcLeg") as $legs_name) {
//    $i = 0;
    foreach ($responseArr[$legs_name] as $leg) {
	  if (isset($leg["polyline"]["encodedPolyline"])) {
        $update_di_id = 0;
        $sqlquery = "SELECT di_id, di_distance, di_duration, di_status, di_modify FROM address_geo.distance WHERE " .
          "di_lat_start = " . round($leg["startLocation"]["latLng"]["latitude"], 6) . " AND " .
          "di_long_start = " . round($leg["startLocation"]["latLng"]["longitude"], 6) . " AND " .
          "di_lat_dest = " . round($leg["endLocation"]["latLng"]["latitude"], 6) . " AND " .
          "di_long_dest = " . round($leg["endLocation"]["latLng"]["longitude"], 6);
//          "di_lat_start = " . $stops[$i][0] . " AND " .
//          "di_long_start = " . $stops[$i][1] . " AND " .
//          "di_lat_dest = " . $stops[$i+1][0] . " AND " .
//          "di_long_dest = " . $stops[$i+1][1];
        writeLog_xServer("get_zone_distance: " . "[" . $sqlquery . "]" . round($leg["endLocation"]["latLng"]["latitude"], 6) . "|" . round($leg["endLocation"]["latLng"]["longitude"], 6));
        $res = $db_geo->query($sqlquery);
        if (DB::isError($res)) reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
        if ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
          writeLog_xServer("get_zone_distance: [(" . round($leg["startLocation"]["latLng"]["latitude"], 6) . ", " . round($leg["startLocation"]["latLng"]["longitude"], 6) . ") -> (" . round($leg["endLocation"]["latLng"]["latitude"], 6) . ", " . round($leg["endLocation"]["latLng"]["longitude"], 6) . ") " .
            "[" . $row["di_distance"] . ", " . $row["di_duration"] . ", " . $row["di_status"] . ", " . $row["di_modify"] . "]");
          $update_di_id = $row["di_id"];
          $update_di_distance = $row["di_distance"];
        endif;
        $res->free();
        if ($update_di_id > 0):
          $sqlquery = 
            "UPDATE address_geo.distance SET" .
//              " di_lat_start = " . round($leg["startLocation"]["latLng"]["latitude"], 6) .
//              ", di_long_start = " . round($leg["startLocation"]["latLng"]["longitude"], 6) .
//              ", di_lat_dest = " . round($leg["endLocation"]["latLng"]["latitude"], 6) .
//              ", di_long_dest = " . round($leg["endLocation"]["latLng"]["longitude"], 6) .
              " di_distance = " . $leg["distanceMeters"] .
              ", di_duration = " . str_replace("s", "", $leg["duration"]) .
              ", di_polyline = '" . str_replace("\\", ".", $leg["polyline"]["encodedPolyline"]) . "'" .
              ", di_status = '" . "OK" . "'" .
              ", di_modify = '" . $di_modify . "'" .
              " WHERE di_id = " . $update_di_id;
          if ($update_di_distance != $leg["distanceMeters"]):
            writeLog_xServer("get_zone_distance: \di_id = " . $update_di_id . ", di_distance old = " . $update_di_distance . ", di_distance new = " . $leg["distanceMeters"] . ", \$shortest = " . $shortest);
          endif;
        else:
          $sqlquery = 
            "INSERT INTO address_geo.distance (di_lat_start, di_long_start, di_lat_dest, di_long_dest, di_distance, di_duration, di_polyline, di_status, di_modify) VALUES (" .
              round($leg["startLocation"]["latLng"]["latitude"], 6) . ", " .
              round($leg["startLocation"]["latLng"]["longitude"], 6) . ", " .
              round($leg["endLocation"]["latLng"]["latitude"], 6) . ", " .
              round($leg["endLocation"]["latLng"]["longitude"], 6) . ", " .
//              $stops[$i][0] . ", " .
//              $stops[$i][1] . ", " .
//              $stops[$i+1][0] . ", " .
//              $stops[$i+1][1] . ", " .
              $leg["distanceMeters"] . ", " . 
              str_replace("s", "", $leg["duration"]) . ", '" . 
              str_replace("\\", ".", $leg["polyline"]["encodedPolyline"]) . "', '" .
              "OK', '" .
              $di_modify . "')";
        endif;
        writeLog_xServer("get_zone_distance: [" . $sqlquery . "]");
        $res = $db_geo->query($sqlquery);
        if (DB::isError($res)): reportDie ("$PHP_SELF: '$sqlquery' : " . $res->getMessage()); endif;
        if ($update_di_id == 0)
          $update_di_id = $db_geo->getOne("SELECT LAST_INSERT_ID()");
//        $distance += $leg["distanceMeters"];
//        $duration += (float) str_replace("s", "", $leg["duration"]);
        $di_ids[$legs_name] .= ($di_ids[$legs_name] != "" ? "," : "") . $update_di_id;
      } else {
        writeLog_xServer("get_zone_distance: [" . "\$leg[\"polyline\"][\"encodedPolyline\"]" . " for " . $legs_name . "]");
      }
//      $i++;
    }
  }
//  $responseArr["distance"] = ($distance / 1000) . " km";
//  $responseArr["duration"] = $duration . "s";
  $responseArr["di_ids"] = $di_ids;
//  writeLog_xServer("get_zone_distance: ". var_export($responseArr, true));
  writeLog_xServer("get_zone_distance: ". json_encode($responseArr));
  return $responseArr;
}

// $start_lat = 0, $start_long = 0, $end_lat = 0, $end_long = 0 für BWV eingeführt
function get_job_distance($jb_id, $mode = "km", $tr_sort = 0, $start_lat = 0, $start_long = 0, $end_lat = 0, $end_long = 0) {
  global $db, $phpVersion, $hq_id;

  writeLog_xServer("get_job_distance: \$jb_id = " . $jb_id . ", \$mode = " . $mode . ", \$tr_sort = " . $tr_sort);
  if (trim($jb_id) == "" || !is_numeric($jb_id) || $jb_id == 0)
    return;
  
  $db_geo = get_db_geo();
  $hq_id = $db->getOne("SELECT hq_id FROM job WHERE jb_id = " . $jb_id);

  if ($mode == "km") {
    // km manuell angepasst?
    $jbprc_remark = $db->getOne("SELECT jbprc_remark FROM jobprice WHERE jb_id = " . $jb_id . " AND mt_sort = 11");
    if (strtolower(trim($jbprc_remark)) == "m" || $jbprc_remark == "c") {
      writeLog_xServer("get_job_distance: km_value is " . ($jbprc_remark == "m" ? "manual" : ($jbprc_remark == "c" ? "jobcalculator" : $jbprc_remark))  . ", nothing to do");
      return array(-1, -1);
    }
  }
  $stops = array();
  $cr_coords = false;
  if ($mode == "traffic") {
    $cr_id = $db->getOne("SELECT cr_id FROM job WHERE jb_id = " . $jb_id);
    if ($cr_id != "") {
      $cr_gps_time = $db->getOne("SELECT cr_gps_time FROM courier WHERE cr_id = " . $cr_id);
// Parameter für Verfallsdatum fehlt noch, hier jetzt hardcoded 15 Min
      if (time() <= strtotime($cr_gps_time) + 15 * 60) {
        $cr_gps_long = $db->getOne("SELECT cr_gps_long FROM courier WHERE cr_id = " . $cr_id);
        $cr_gps_lat = $db->getOne("SELECT cr_gps_lat FROM courier WHERE cr_id = " . $cr_id);
        $stops[0] = array($cr_gps_lat, $cr_gps_long);
        $cr_coords = true;
      } else {
        writeLog_xServer("get_job_distance: coords are too old for \$cr_id = " . $cr_id . " (\$cr_gps_time = " . $cr_gps_time . ")");
      }
    } else {
      writeLog_xServer("get_job_distance: \$cr_id = " . $cr_id . " (i. e. error: no courier associated)");
      return array(-1, -1);
	}
  }
  $error_station = 0;
  $station_count = 0;
  $last_finishtime = "0000-00-00 00:00:00";
  $last_finish_tr_sort = 0;
  $sqlquery = "SELECT tr_sort, tr_status, tr_finishtime, ad_street, ad_zipcode, ad_city, ad_country, tr_hsno " .
    "FROM tour, address WHERE jb_id = " . $jb_id . " AND tour.ad_id = address.ad_id ORDER BY tr_sort";
  $res = $db->query($sqlquery);
  if (DB::isError($res)) die ("$PHP_SELF: '$sqlquery' : " . $res->getMessage());
  while ($row = ($phpVersion >= "8.0" ? $res->fetch_assoc() : $res->fetchRow())):
    $coords = get_coords($row["ad_country"], $row["ad_zipcode"], $row["ad_city"], $row["ad_street"], $row["tr_hsno"]);
    if ($coords[0] != 0 && $coords[1] != 0) {
      if ($mode != "traffic" || ($row["tr_status"] == 0 && $row["tr_sort"] <= $tr_sort)) {
        if ($mode == "traffic" && !$cr_coords && time() > strtotime($last_finishtime) + 15 * 60) {
          writeLog_xServer("courier-coords are too old and no finished station or finished station is too old (\$last_finishtime = " . $last_finishtime . ", \$last_finish_tr_sort = " . $last_finish_tr_sort . ")");
          return array(-1, -1);          
        }
        $stops[] = $coords;
        if ($row["tr_sort"] < $tr_sort)
          $station_count++;
      } elseif(!$cr_coords && $row["tr_status"] == 1) {		
        $stops[0] = $coords;
        $last_finishtime = $row["tr_finishtime"];
        $last_finish_tr_sort = $row["tr_sort"];
	  }
    } else {
      $error_station = $row["tr_sort"];
      break;
    }
  endwhile;
  $res->free();

  if ($start_lat != 0 && $start_long != 0 && $end_lat != 0 && $end_long != 0) {
    array_unshift($stops, array($start_lat, $start_long));
    $stops[] = array($end_lat, $end_long);
  }
  writeLog_xServer("get_job_distance: " . json_encode($stops));
  $total_km = 0;
  if (count($stops) > 0) {
    if ($error_station == 0) {
      list($total_km, $total_km_osm) = get_tour_distance($stops, '0000-00-00 00:00:00', $mode);
    } else {
      if ($mode == "traffic") {
        $total_km = -1;
	  }
      writeLog_xServer("get_job_distance: Adresse in Station " . $error_station . " nicht ok");
    }
  } else {
    if ($mode == "traffic") {
      $total_km = -1;
    }
    writeLog_xServer("get_job_distance: job has one station only");
  }
  if ($mode == "km" && $start_lat == 0 && $start_long == 0 && $end_lat == 0 && $end_long == 0) {
    $db->query("DELETE FROM jobprice WHERE jb_id = " . $jb_id . " AND mt_sort = 11");
    writeLog_xServer("get_job_distance: [INSERT INTO jobprice (jb_id, mt_sort, jbprc_price, jbprc_remark) VALUES (" . $jb_id . ", 11, " . $total_km . ", '" . $total_km_osm . "')]");
    $db->query("INSERT INTO jobprice (jb_id, mt_sort, jbprc_price, jbprc_remark) VALUES (" . $jb_id . ", 11, " . $total_km . ", '" . $total_km_osm . "')");
  }
  writeLog_xServer("get_job_distance: \$total_km = " . $total_km . ", \$mode = " . $mode . ", " . ($mode == "km" ? "\$total_km_osm = " . $total_km_osm : "\$station_count = " . $station_count));
  return array($total_km, ($mode == "km" ? $error_station : $station_count));
//  return array($total_km, ($mode == "km" ? $total_km_osm : $station_count));
}

function get_tour_duration($tr_id) {
  global $db;

  writeLog_xServer("get_tour_duration: \$tr_id = " . $tr_id);
  $gdc_content = $db->getOne("SELECT gdc_content FROM genericdatacontainer WHERE gdc_obj_type = 'tr' AND gdc_gen_fieldname = 'tr_tracking' AND gdc_obj_id = " . $tr_id);
  $gdc_content_arr = explode("|", $gdc_content);
// Parameter für Verfallsdatum fehlt noch, hier jetzt hardcoded 15 Min (ebenso wie oben die 15 Minuten für das Höchstalter der letzten Ortung)
  if (count($gdc_content_arr) > 1) {
    //if (time() < strtotime($modify_date) + getParameterValue("0", "GEOCACHE_LIFETIME", "0") * 24 * 3600)
    if (time() < strtotime($gdc_content_arr[1]) + 15 * 60 && $gdc_content_arr[2] != -1) {
      $duration_in_traffic = $gdc_content_arr[2] - (time() - strtotime($gdc_content_arr[1]));
      writeLog_xServer("get_tour_duration: " . (15 * 60 - (time() - strtotime($gdc_content_arr[1]))) . " secs until next request (\$tr_id = " . $tr_id . ", \$gdc_content_arr[1] = " . $gdc_content_arr[1] . ", \$gdc_content_arr[2] = " . $gdc_content_arr[2] . ", \$duration_in_traffic = " . $duration_in_traffic . ")");
      return $duration_in_traffic;
    }
  }
  $jb_id = $db->getOne("SELECT jb_id FROM tour WHERE tr_id = " . $tr_id);
  $tr_sort = $db->getOne("SELECT tr_sort FROM tour WHERE tr_id = " . $tr_id);
  list($duration_in_traffic, $station_count) = get_job_distance($jb_id, "traffic", $tr_sort);

  $csc_id_payer = $db->getOne("SELECT csc_id_payer FROM job WHERE jb_id = " . $jb_id);
  $cs_id = $db->getOne("SELECT cs_id FROM costcenter WHERE csc_id = " . $csc_id_payer);
  $cs_charging_time = $db->getOne("SELECT cs_charging_time FROM customer WHERE cs_id = " . $cs_id);

//  $duration_in_traffic = round($duration_in_traffic / 60 * 2 / 10) / 2 * 10 * 60 + $cs_charging_time * $station_count * 60;
  if ($duration_in_traffic != -1)
    $duration_in_traffic += $cs_charging_time * $station_count * 60;
  updateStmt("genericdatacontainer", "gdc_obj_id", $tr_id, array("gdc_content", $gdc_content_arr[0] . "|" . date("Y-m-d H:i:s") . "|" . $duration_in_traffic), "gdc_obj_type = 'tr' AND gdc_gen_fieldname = 'tr_tracking'");
  writeLog_xServer("get_tour_duration: \$jb_id = " . $jb_id . ", \$tr_id = " . $tr_id . ", \$duration_in_traffic = " . $duration_in_traffic);
  return $duration_in_traffic;
}
/*******************************************************************************

Working:

<?xml version="1.0" encoding="utf-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://exception.core.jabba.ptvag.com" xmlns:ns1="http://common.xserver.ptvag.com" xmlns:ns2="http://xsequence.xserver.ptvag.com" xmlns:ns3="http://types.xsequence.xserver.ptvag.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Body>
    <ns3:planTimewindowTour>
      <ns3:ArrayOfTimewindowStop_1>
        <ns2:TimewindowStop servicePeriod="1800" stopID="1" xsi:type="ns2:TimewindowStop">
          <ns2:loc x="983547" y="5362310"/>
          <ns2:wrappedOpeningIntervals>
            <ns2:Interval from="28800" till="50400"/>
          </ns2:wrappedOpeningIntervals>
        </ns2:TimewindowStop>
        <ns2:TimewindowStop servicePeriod="1800" stopID="2" xsi:type="ns2:TimewindowStop">
          <ns2:loc x="997272" y="5367644"/>
          <ns2:wrappedOpeningIntervals>
            <ns2:Interval from="46061" till="68400"/>
          </ns2:wrappedOpeningIntervals>
        </ns2:TimewindowStop>
        <ns2:TimewindowStop servicePeriod="1800" stopID="3" xsi:type="ns2:TimewindowStop">
          <ns2:loc x="1043632" y="5341378"/>
          <ns2:wrappedOpeningIntervals>
            <ns2:Interval from="28800" till="50400"/>
          </ns2:wrappedOpeningIntervals>
        </ns2:TimewindowStop>
      </ns3:ArrayOfTimewindowStop_1>
      <ns3:TimewindowPlanningParams_2 xsi:type="ns2:TimewindowPlanningParams">
        <ns2:distanceCalculation dimaCoordFormat="GEODECIMAL" drivingPeriodFactor="1.0"/>
      </ns3:TimewindowPlanningParams_2>
      <ns3:InputTour_3 xsi:type="ns2:InputTour">
        <ns2:wrappedInputTourPoints>
          <ns2:InputTourPoint stopID="1"/>
          <ns2:InputTourPoint stopID="2"/>
          <ns2:InputTourPoint stopID="3"/>
        </ns2:wrappedInputTourPoints>
        <ns2:subSequence predPos="0" succPos="4"/>
      </ns3:InputTour_3>
      <ns3:CallerContext_4 xsi:nil="1"/>
    </ns3:planTimewindowTour>
  </env:Body>
</env:Envelope>

Not working:

<?xml version="1.0" encoding="utf-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://exception.core.jabba.ptvag.com" xmlns:ns1="http://common.xserver.ptvag.com" xmlns:ns2="http://xsequence.xserver.ptvag.com" xmlns:ns3="http://types.xsequence.xserver.ptvag.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Body>
    <ns3:planTimewindowTour>
      <ns3:ArrayOfTimewindowStop_1>
        <ns2:TimewindowStop servicePeriod="1800" stopID="1" xsi:type="ns2:TimewindowStop">
          <ns2:loc x="983547" y="5362310"/>
          <ns2:wrappedOpeningIntervals>
            <ns2:Interval from="28800" till="50400"/>
          </ns2:wrappedOpeningIntervals>
        </ns2:TimewindowStop>
        <ns2:TimewindowStop servicePeriod="1800" stopID="2" xsi:type="ns2:TimewindowStop">
          <ns2:loc x="997272" y="5367644"/>
          <ns2:wrappedOpeningIntervals>
            <ns2:Interval from="46062" till="68400"/>
          </ns2:wrappedOpeningIntervals>
        </ns2:TimewindowStop>
        <ns2:TimewindowStop servicePeriod="1800" stopID="3" xsi:type="ns2:TimewindowStop">
          <ns2:loc x="1043632" y="5341378"/>
          <ns2:wrappedOpeningIntervals>
            <ns2:Interval from="28800" till="50400"/>
          </ns2:wrappedOpeningIntervals>
        </ns2:TimewindowStop>
      </ns3:ArrayOfTimewindowStop_1>
      <ns3:TimewindowPlanningParams_2 xsi:type="ns2:TimewindowPlanningParams">
        <ns2:distanceCalculation dimaCoordFormat="GEODECIMAL" drivingPeriodFactor="1.0"/>
      </ns3:TimewindowPlanningParams_2>
      <ns3:InputTour_3 xsi:type="ns2:InputTour">
        <ns2:wrappedInputTourPoints>
          <ns2:InputTourPoint stopID="1"/>
          <ns2:InputTourPoint stopID="2"/>
          <ns2:InputTourPoint stopID="3"/>
        </ns2:wrappedInputTourPoints>
        <ns2:subSequence predPos="0" succPos="4"/>
      </ns3:InputTour_3>
      <ns3:CallerContext_4 xsi:nil="1"/>
    </ns3:planTimewindowTour>
  </env:Body>
</env:Envelope>
*******************************************************************************/

?>
