Merge branch 'fronkdev' into 'master'

ConstructionConsent: Plan from Mapbox with Rimo coords

See merge request fronk/thetool!923
This commit is contained in:
Frank Schubert
2025-01-20 13:00:19 +00:00
5 changed files with 553 additions and 12 deletions

View File

@@ -153,10 +153,48 @@
<hr />
<h4>Planskizze / Bilddatei</h4>
<div class="form-group row">
<label class="col-lg-2 col-form-label" for="name">Planskizze / Bilddatei</label>
<label class="col-lg-2 col-form-label"></label>
<div class="col-lg-10">
<input type="file" class="form-control" name="consent_plan_image" id="consent_plan_image" />
<div class="card">
<h5 class="card-header">Plan aus Rimo laden</h5>
<div class="card-body">
<select class="form-control" name="plan_adb_hausnummer_id" id="plan_adb_hausnummer_id">
<?php if(isset($item) && $item->plan_adb_hausnummer_id): ?>
<option value="<?=$item->plan_adb_hausnummer_id?>" selected="selected"><?=$item->plan_adb_hausnummer->street->name?> <?=$item->plan_adb_hausnummer->hausnummer?>, <?=$item->plan_adb_hausnummer->street->gemeinde->name?></option>
<?php endif; ?>
</select>
</div>
</div>
<div class="card">
<h5 class="card-header">Oder Plan hochladen</h5>
<div class="card-body">
<input type="file" class="form-control" name="consent_plan_image" id="consent_plan_image" />
</div>
</div>
<div class="card">
<h5 class="card-header">Plan Vorschau</h5>
<div class="card-body" id="rimo-plan-container">
<input type="hidden" name="submit_plan_file_id" id="submit_plan_file_id" value="" />
<img id="plan_preview" style="max-width: 1200px;" <?=($item->file) ? "src=".$item->file->file->asDataUrl()."" : "" ?> />
<div class="row mt-2">
<div class="col">
<button type="button" class="btn btn-outline-danger hidden" id="delete-rimo-plan" onclick="deleteRimoPlan()">
<i class="far fa-fw fa-trash-alt"></i> Plan löschen
</button>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -222,16 +260,74 @@
placeholder: "Suche nach Straße",
allowClear: true
});
$('#adb_strasse_id').on('select2:close', function(e) {
if(!$('#adb_strasse_id').val()) {
$('#new-address-toggle').show();
$('#plan_adb_hausnummer_id').select2({
ajax: {
url: '<?=self::getUrl("ConstructionConsent", "api")?>',
data: (params) => {
return {
q: params.term,
do: "findAddress",
project_id: $("#constructionconsentproject_id :selected").val()
}
},
delay: 250,
dataType: 'json'
},
minimumInputLength: 2,
placeholder: "Suche nach Adresse",
allowClear: true
});
$('#plan_adb_hausnummer_id').change( async () => {
if(!$("#plan_adb_hausnummer_id").val()) return;
var building_id = $('#plan_adb_hausnummer_id').val();
// get plan image preview
try {
var response = await fetch('<?=self::getUrl("ConstructionConsent", "Api", ["do" => "getRimoPlanPreview"])?>&building_id=' + building_id);
if (!response.ok) {
return false;
}
var resp_json = await response.json();
var plan_data = resp_json.result;
var img_mimetype = plan_data.image_mimetype;
var img_base64 = plan_data.image_base64;
var file_id = plan_data.file_id;
$("#plan_preview").attr("src" ,"data:" + img_mimetype + ";base64," + img_base64);
$("#submit_plan_file_id").val(file_id);
$("#delete-rimo-plan").show();
} catch (error) {
console.log("Exception fetching plan preview:");
console.log(error);
return false;
}
});
$("#consent_plan_image").change(() => {
var [file] = $("#consent_plan_image").prop("files");
if(file) {
$("#plan_preview").attr("src", URL.createObjectURL(file));
}
});
$("#constructionconsentproject_id").change(() => {
$("#adb_strasse_id").val("").change();
});
function deleteRimoPlan() {
$('#plan_adb_hausnummer_id').val("");
$("#submit_plan_file_id").val();
$("#plan_preview").attr("src", "");
$("#delete-rimo-plan").hide();
}
</script>
<?php include(realpath(dirname(__FILE__) . "/../../$mfLayoutPackage") . "/footer.php"); ?>

View File

@@ -247,24 +247,32 @@ class ConstructionConsentController extends mfBaseController {
return $this->editAction();
}
} else {
if($r->submit_plan_file_id) {
$file = new File($r->submit_plan_file_id);
}
}
if($file && $file->id) {
$ccf = ConstructionConsentFile::create([
'constructionconsent_id' => $id,
'file_id' => $file->id,
'filename' => "zustimmungserklärung-".$item->id."-plan.png",
'filename' => "zustimmungserklärung-" . $item->id . "-plan.png",
]);
// delete previous image
$img = ConstructionConsentFile::getFirst(["constructionconsent_id" => $id]);
if($img) {
if ($img) {
$img->file->delete();
$img->delete();
}
if(!$ccf->save()) {
$this->layout()->setFlash("Fehler beim Hochladen", "warning");
if (!$ccf->save()) {
$this->layout()->setFlash("Fehler beim Speichern des Plans", "warning");
}
}
$this->layout()->setFlash("Zustimmungserklärung erfolgreich gespeichert", "success");
$this->redirect("ConstructionConsent", "View", ["id" => $id]);
@@ -281,6 +289,15 @@ class ConstructionConsentController extends mfBaseController {
case "findStreet":
$return = $this->findStreetApi();
break;
case "findAddress":
$return = $this->findAddressApi();
break;
case "getRimoPlanPreview":
$return = $this->getRimoPlanPreviewApi();
break;
case "saveRimoPlanPreview":
$return = $this->saveRimoPlanPreviewApi();
break;
default:
$this->log->warn(__METHOD__ . ": Called API function '$do' does not exist");
$return = false;
@@ -425,4 +442,276 @@ class ConstructionConsentController extends mfBaseController {
exit;
}
private function findAddressApi() {
$addresses = [];
$search = trim($this->request->q);
$project_id = $this->request->project_id;
$include_gst = $this->request->include_gst ? $this->request->include_gst : false;
$scluster_ids = [];
if($project_id) {
$project = new ConstructionConsentProject($project_id);
if(!$project->id) {
header("Content-Type: application/json");
echo json_encode(["results" => []]);
exit;
}
foreach($project->adb_networks as $network) {
$scluster_ids[] = $network->id;
}
} else {
// get all salesclusters
foreach(ADBNetzgebietModel::getAll() as $network) {
$scluster_ids[] = $network->id;
}
}
$results = [];
$search_parts = explode(" ", $search);
$ort_search = $strasse_search = $plz_search = $hausnummer_search = $gst_search = [];
foreach($search_parts as $p) {
$p = $this->db->escape(trim($p));
if(!$p) continue;
$ort_search[] = "ortschaft like '$p%'";
$strasse_search[] = "strasse like '$p%'";
$plz_search[] = "plz like '%$p%'";
$hausnummer_search[] = "hausnummer like '%$p%'";
$gst_search[] = "grund_nr like '%$p%'";
}
$where = "1=1";
if(count($scluster_ids)) {
$where .= " AND netzgebiet_id IN (".implode(', ',$scluster_ids).")";
}
if($include_gst) {
$sql = "SELECT * FROM view_hausnummer WHERE $where AND ((".implode(" OR ", $ort_search).") OR (".implode(" OR ", $strasse_search).") OR (".implode(" OR ", $plz_search).") OR (".implode(" OR ", $hausnummer_search).") OR (".implode(" OR ", $gst_search).") ) ORDER BY strasse, LENGTH(hausnummer), hausnummer";
} else {
$sql = "SELECT * FROM view_hausnummer WHERE $where AND ((".implode(" OR ", $ort_search).") OR (".implode(" OR ", $strasse_search).") OR (".implode(" OR ", $plz_search).") OR (".implode(" OR ", $hausnummer_search).")) ORDER BY strasse, LENGTH(hausnummer), hausnummer";
}
$this->log->debug($sql);
$adb = FronkDB::singleton(ADDRESSDB_DBHOST, ADDRESSDB_DBUSER, ADDRESSDB_DBPASS, ADDRESSDB_DBNAME);
$res = $adb->query($sql);
$this->log->debug("done");
if(!$adb->num_rows($res)) {
header("Content-Type: application/json");
echo json_encode(["results" => []]);
exit;
}
while($data = $adb->fetch_object($res)) {
$address_string = $data->plz." ".$data->ortschaft.", ".$data->strasse." ".$data->hausnummer;
if($include_gst) {
$address_string .= " | GST: ".$data->grund_nr;
}
$sort_key = $data->plz." ".$data->ortschaft." ".$data->strasse;
$address = [];
$address['id'] = $data->hausnummer_id;
$address["text"] = $address_string;
$address['sort_key'] = $sort_key;
$addresses[] = $address;
}
// sort results by most occurences of search strings
$sort = [];
foreach($addresses as $key => $address) {
$includes_int = false;
$count = 0;
foreach($search_parts as $p) {
$p = $this->db->escape(trim($p));
if(!$p) continue;
if(is_numeric(($p))) {
$includes_int = true;
if(substr_count(strtolower($address['text']), strtolower($p))) {
$count++;
}
} else {
$count += substr_count(strtolower($address['text']), strtolower($p));
}
}
unset($address['sort_key']);
//echo $address['text']." $p $count<br />\n";
if($includes_int && (($count + 1) - count($search_parts) ) < 1) {
continue;
}
if(!array_key_exists($count, $sort)) {
$sort[$count] = [];
}
$sort[$count][] = $address;
}
ksort($sort, SORT_NUMERIC);
$sort = array_reverse($sort, true);
//var_dump($sort);exit;
foreach($sort as $res) {
foreach($res as $a) {
$results[] = $a;
}
}
header("Content-Type: application/json");
echo json_encode(["results" => $results]);
exit;
}
private function getRimoPlanPreviewApi() {
$adb_hausnummer_id = $this->request->building_id;
$hausnummer = new ADBHausnummer($adb_hausnummer_id);
if(!$hausnummer->id) {
return false;
}
$filename = "consent_plan_map_h{$adb_hausnummer_id}";
/*$bpi_file = PreorderFile::getFirst(["preorder_id" => $this->id, "filename" => $filename]);
if($bpi_file) {
return $bpi_file;
}*/
// get trenches from rimo
$geodataResponse = Rimoapi::getBuildingGeoJson($hausnummer->rimo_id);
$this->log->debug(__METHOD__.": ".print_r($geodataResponse, true));
//return false;
if (is_object($geodataResponse)) {
if (property_exists($geodataResponse, "homeSection")) {
if (property_exists($geodataResponse->homeSection, "features") && is_array($geodataResponse->homeSection->features)) {
foreach ($geodataResponse->homeSection->features as $feature) {
$home_trench = [];
foreach ($feature->geometry->coordinates as $coords) {
$long = $coords[0];
$lat = $coords[1];
$home_trench[] = [$lat, $long];
}
if ($hausnummer->home_trench != $home_trench) {
$hausnummer->home_trench = json_encode($home_trench);
$hausnummer->save();
}
}
}
}
if (property_exists($geodataResponse, "borderPoint")) {
if (property_exists($geodataResponse->borderPoint, "features") && is_array($geodataResponse->borderPoint->features)) {
foreach ($geodataResponse->borderPoint->features as $feature) {
$coords = $feature->geometry->coordinates;
$long = $coords[0];
$lat = $coords[1];
if ($hausnummer->borderpoint_lat != $lat || $hausnummer->borderpoint_long != $long) {
$hausnummer->borderpoint_lat = $lat;
$hausnummer->borderpoint_long = $long;
$hausnummer->save();
}
}
}
}
if (property_exists($geodataResponse, "trenches") && $geodataResponse->trenches->features) {
$trenches = [];
foreach ($geodataResponse->trenches->features as $feature) {
$feature_coords = [];
foreach ($feature->geometry->coordinates as $coords) {
$long = $coords[0];
$lat = $coords[1];
$feature_coords[] = [$lat, $long];
}
$trenches[] = $feature_coords;
}
if (count($trenches)) {
$hausnummer->trenches = json_encode($trenches);
$hausnummer->save();
}
}
}
// get new Borderpoint Image from Mapbox API
$params = [
"pin" => null,
"gps_lat" => $hausnummer->gps_lat,
"gps_long" => $hausnummer->gps_long,
"zoom" => 19,
"size_x" => 640,
"size_y" => 640,
"style" => "satellite-streets-v12",
"paths" => "",
"access_token" => TT_MAPBOX_TILE_API_TOKEN
];
if($hausnummer->trenches) {
$trenches = json_decode($hausnummer->trenches);
$params["paths"] = [
"line_width" => 5,
"line_color" => "ff0000",
"line_opacity" => 1,
"line_fill_color" => "ff0000",
"line_fill_opacity" => 1,
"coords" => $trenches
];
}
$image_content = Mapbox_StaticImageApi::getImageFileContent($params);
if(!$image_content) {
return false;
}
$fs_filename = "$filename.jpg";
if(!file_put_contents(MFUPLOAD_FILE_SAVE_PATH."/".TT_CONSTRUCTIONCONSENT_FILE_UPLOAD_SUBFOLDER."/$fs_filename", $image_content)) {
$this->log->error(__METHOD__.": Error saving Borderpoint Static Map Image File");
return false;
}
$file = FileModel::create([
"name" => "consent_plan_map",
"description" => $adb_hausnummer_id,
"filename" => "$filename.jpg",
"orig_filename" => "$filename.jpg",
"store_filename" => $fs_filename,
"subfolder" => TT_CONSTRUCTIONCONSENT_FILE_UPLOAD_SUBFOLDER,
]);
if(!$file->save()) {
$this->log->error(__METHOD__.": Error saving File Object");
return false;
}
$file->mimetype = $file->getMimetype();
$file->save();
return ["image_mimetype" => $file->mimetype, "image_base64" => base64_encode($image_content), "file_id" => $file->id];
/*$pf = PreorderFile::create([
"preorder_id" => $this->id,
"file_id" => $file->id,
"filename" => $filename
]);
if(!$pf->save()) {
$this->log->error(__METHOD__.": Error saving PreorderFile Object");
return false;
}*/
//return $pf;
}
private function saveRimoPlanPreviewApi() {
$adb_hausnummer_id = $this->request->building_id;
$consent_id = $this->request->constructionconsent_id;
}
}

View File

@@ -11,6 +11,7 @@ class Mapbox_StaticImageApi {
$size_y = $params["size_y"];
$style = $params["style"];
$pin = $params["pin"];
$paths = $params["paths"];
$pin_part = "";
@@ -23,12 +24,92 @@ class Mapbox_StaticImageApi {
$pin_color = $pin["color"];
$pin_icon = $pin["icon"];
$pin_part .= "/pin-$pin_size";
$pin_part .= "pin-$pin_size";
if($pin_icon) $pin_part .= "-$pin_icon";
$pin_part .= "+$pin_color($pin_gps_long,$pin_gps_lat)";
}
$url .= "$pin_part/$gps_long,$gps_lat,$zoom/{$size_x}x{$size_y}?access_token=$access_token";
$path_parts = [];
if(is_array($paths) && count($paths)) {
$path_stroke_width = $paths["line_width"];
$path_stroke_color = $paths["line_color"];
$path_stroke_opacity = $paths["line_opacity"];
$path_fill_color = $paths["line_fill_color"];
$path_fill_opacity = $paths["line_fill_opacity"];
$path_count = 0;
$initial_path_count = 0;
foreach($paths["coords"] as $path) {
$initial_path_count++;
//mfLoghandler::singleton()->debug(print_r($paths, true));
$skip_path = false;
// skip if coordinates are outside of 111 meters of middle point
// because mapbox only allows 100 paths max
foreach($path as $coords) {
$lat_less = $gps_lat;
$lat_more = $coords[0];
$long_less = $gps_long;
$long_more = $coords[1];
if($coords[0] < $gps_lat) {
$lat_less = $coords[0];
$lat_more = $gps_lat;
}
if($coords[1] < $gps_long) {
$long_less = $coords[1];
$long_more = $gps_long;
}
mfLoghandler::singleton()->debug(__METHOD__.": lat_more($lat_more) - lat_less($lat_less) = ".($lat_more - $lat_less));
mfLoghandler::singleton()->debug(__METHOD__.": long_more($long_more) - long_less($long_less) = ".($long_more - $long_less));
if($lat_more - $lat_less > 0.0005 || $long_more - $long_less > 0.0005) {
mfLoghandler::singleton()->debug(__METHOD__.": Skipping path");
$skip_path = true;
}
}
if($skip_path) continue;
if(!is_array($path) || !count($path)) {
mfLoghandler::singleton()->debug("path not array or empty: ".print_r($path, true));
continue;
}
if($path_count > 99) break;
$path_count++;
$path_enc_polyline = urlencode(self::encodeCoordArrayToPolyline($path));
//$path_enc_polyline = self::encodeCoordArrayToPolyline($path);
//if(!$path_enc_polyline) continue;
//$pp = "path-$path_stroke_width+$path_stroke_color-$path_stroke_opacity+$path_fill_color-$path_fill_opacity($path_enc_polyline)"
$pp = "path-$path_stroke_width+$path_stroke_color-$path_stroke_opacity($path_enc_polyline)";
$path_parts[] = $pp;
}
}
mfLoghandler::singleton()->debug(__METHOD__.": path count: $path_count | initial path count: $initial_path_count");
// build url
$url_opt_parts = [];
if($pin_part) $url_opt_parts[] = $pin_part;
if(count($path_parts)) {
foreach($path_parts as $path_part) {
$url_opt_parts[] = $path_part;
}
}
if(is_array($url_opt_parts)) {
$url .= "/".implode(",", $url_opt_parts);
}
$url .= "/$gps_long,$gps_lat,$zoom/{$size_x}x{$size_y}?access_token=$access_token";
//$url .= "/auto/{$size_x}x{$size_y}?access_token=$access_token";
mfLoghandler::singleton()->debug($url);
//exit;
$ctx_opts = [
'http' => [
@@ -39,6 +120,7 @@ class Mapbox_StaticImageApi {
$ctx = stream_context_create($ctx_opts);
$response = file_get_contents($url, false, $ctx);
//fLoghandler::singleton()->debug(print_r($response, true));
if($response === false) {
return null;
@@ -47,4 +129,44 @@ class Mapbox_StaticImageApi {
return $response;
}
public static function encodeCoordArrayToPolyline($coords, $precision = 5) {
//if(!is_array($coords)) return false;
$points = [];
array_walk_recursive(
$coords,
function ($current) use (&$points) {
$points[] = $current;
}
);
mfLoghandler::singleton()->debug(__METHOD__.": flattened path: ".print_r($points, true));
$encodedString = '';
$index = 0;
$previous = array(0,0);
foreach ( $points as $number ) {
$number = (float)($number);
$number = (int)round($number * pow(10, $precision));
$diff = $number - $previous[$index % 2];
$previous[$index % 2] = $number;
$number = $diff;
$index++;
$number = ($number < 0) ? ~($number << 1) : ($number << 1);
$chunk = '';
while ( $number >= 0x20 ) {
$chunk .= chr((0x20 | ($number & 0x1f)) + 63);
$number >>= 5;
}
$chunk .= chr($number + 63);
$encodedString .= $chunk;
}
mfLoghandler::singleton()->debug(__METHOD__.": encoded polyline: $encodedString");
//return str_replace("?", "%3f", $encodedString);
return $encodedString;
}
}

View File

@@ -430,5 +430,39 @@ class Rimoapi {
return $resp_data;
}
public static function getBuildingGeoJson($building_id) {
if(!$building_id) return false;
$log = mfLoghandler::singleton();
$params = [];
$params['apiKey'] = RIMO_API_JSON_APIKEY;
$params['buildingId'] = $building_id;
$ctx_opts = [
'http' => [
'method' => 'GET',
'header' => 'accept: application/json'
]
];
$qs = http_build_query($params);
$getFileEp = RIMO_API_JSON_URL.RIMO_API_JSON_EP_GET_JSON_FOR_BUILDING;
$get_url = $getFileEp."?".$qs;
$ctx = stream_context_create($ctx_opts);
$log->debug(__METHOD__.": Getting GeoJson from Rimo: $get_url");
$response = file_get_contents($get_url, false, $ctx);
if($response === false) {
$log->error("Error retrieving GeoJson from RIMO for $building_id");
return false;
}
$resp_data = json_decode($response);
return $resp_data;
}
}

View File

@@ -680,7 +680,7 @@ foreach ($clusters as $cluster_data) {
if($wo_home_external_id) {
$wo_home = \ADBWohneinheitModel::getFirst(["extref" => $wo_home_external_id]);
if($wo_home != $wo->adb_wohneinheit_id) {
$addressErrors[] = "Wohneinheit für Workorder ".$wo->name." hat sich geändert von ".$wo->adb_wohneinheit_id." auf ".$wo_home->extref." (aber wurde nicht im Tool übernommen)";
$addressErrors[] = "Wohneinheit für Workorder ".$wo->rimo_name." hat sich geändert von ".$wo->adb_wohneinheit_id." auf ".$wo_home->extref." (aber wurde nicht im Tool übernommen)";
}
} else {
$addressErrors[] = "Wohneinheit für Workorder ".$wo->name." ist jetzt leer";