瀏覽代碼

fixed performance for getFeature api for qgis

Piotr Labudda 10 年之前
父節點
當前提交
e210e627fb

+ 5 - 2
SE/se-lib/Api/WfsData.php

@@ -5,6 +5,7 @@ Lib::loadClass('ApiDataSourceTodo');// TODO: @see Entity/Source/Mysql from featu
 Lib::loadClass('Api_WfsException');
 Lib::loadClass('Api_WfsDataServer');
 Lib::loadClass('UserAcl');
+Lib::loadClass('Api_WfsLogger');
 
 class Api_WfsData extends ApiRouteBase {// TODO: extends Api_WfsBase which extends ApiBase
 
@@ -16,7 +17,9 @@ class Api_WfsData extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 
 	public function execute($request) {
 
-		$this->reqDBG($request, __LINE__);
+		$wfsLogger = new Api_WfsLogger();
+		$this->setLogger($wfsLogger);
+		$this->reqDBG($request);
 
 		/* TODO: return response xml document
 		$responseDocument = null;
@@ -81,7 +84,7 @@ class Api_WfsData extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 		$userAcl->fetchAllPerms(true);
 		IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$userAcl (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($userAcl);echo'</pre>';}
 
-		$this->DBG("WfsServer(" . $this->_apiUser->getID() . ") ...", __LINE__);
+		$this->DBG("usr:" . $this->_apiUser->getID(), __LINE__, __FUNCTION__, __CLASS__);
 		$wfsServer = new Api_WfsDataServer($userAcl);
 		$wfsServer->setBaseUri($this->_apiBaseUri);
 

+ 89 - 0
SE/se-lib/Api/WfsGeomTypeConverter.php

@@ -176,4 +176,93 @@ if($DBG){echo 'gmlCoordinatesFromWkt:';print_r($gmlCoordinatesFromWkt);echo "\n"
 		return $geomNode;
 	}
 
+	public function createGmlFromWkt_xmlWriter($geomAsWkt, $xmlWriter) {
+		$DBG = (V::get('DBG_GEO', '', $_GET) > 0);// TODO: Profiler
+		if (empty($geomAsWkt)) return null;
+
+		// WKT: http://en.wikipedia.org/wiki/Well-known_text
+if($DBG){echo 'wkt:';print_r($geomAsWkt);echo "\n";}
+		$gmlCoordinatesFromWkt = '';
+		$bbox = null;
+		$bboxPolygon = null;
+		$wktType = substr($geomAsWkt, 0, strpos($geomAsWkt, '('));
+		$sql = "select
+		--	GeometryType(GeomFromText('{$geomAsWkt}')) as type,
+			AsText(Envelope(GeomFromText('{$geomAsWkt}'))) as bbox
+		";
+		$db = DB::getDB();
+		$res = $db->query($sql);
+		if ($r = $db->fetch($res)) {
+			$bboxPolygon = $r->bbox;
+		}
+if($DBG){echo 'wktType:';print_r($wktType);echo "\n";}
+if($DBG){echo 'bboxPolygon:';print_r($bboxPolygon);echo "\n";}
+		if ($bboxPolygon) {// eg. POLYGON((18.550927583884 54.3124269117497,18.6401914998997 54.3124269117497,18.6401914998997 54.3608667397837,18.550927583884 54.3608667397837,18.550927583884 54.3124269117497))
+			$bbox = '';
+			$wktPoint = '(\d+\.?\d*) (\d+\.?\d*)';
+			$wktPolygonPattern = '/^POLYGON\(\(' . $wktPoint . ','  . $wktPoint . ',' . $wktPoint . ',' . $wktPoint . ',' . $wktPoint . '\)\)$/';
+if($DBG){echo 'wktPolygonPattern:';print_r($wktPolygonPattern);echo "\n";}
+			if (preg_match($wktPolygonPattern, $bboxPolygon, $matches)) {
+				$bbox = $matches;
+			}
+		}
+if($DBG){echo 'bbox:';print_r($bbox);echo "\n";}
+
+		if ('POLYGON' === $wktType) {
+			$gmlCoordinatesFromWkt = $geomAsWkt;
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, strlen('POLYGON(('));
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, 0, -1 * strlen('))'));
+			$gmlType = 'Polygon';
+		}
+		else if ('LINESTRING' === $wktType) {
+			$gmlCoordinatesFromWkt = $geomAsWkt;
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, strlen('LINESTRING('));
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, 0, -1 * strlen(')'));
+			$gmlType = 'LineString';
+		}
+		else if ('POINT' === $wktType) {
+			$gmlCoordinatesFromWkt = $geomAsWkt;
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, strlen('POINT('));
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, 0, -1 * strlen(')'));
+			$gmlCoordinatesFromWkt = str_replace(' ', ',', $gmlCoordinatesFromWkt);
+			$gmlType = 'Point';
+		}
+		else {
+			return null;
+		}
+if($DBG){echo 'gmlCoordinatesFromWkt:';print_r($gmlCoordinatesFromWkt);echo "\n";}
+
+		//$xmlWriter->startElement("gml:{$gmlType}");
+		$xmlWriter->startElementNS('gml', $gmlType, 'http://www.opengis.net/gml');
+		$xmlWriter->writeAttribute('srsName', "http://www.opengis.net/gml/srs/epsg.xml#4326");// TODO: EPSG
+		if ('Point' === $gmlType) {
+			$xmlWriter->startElement("gml:coordinates");
+				//$coordinatesNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+				//$coordinatesNode->setAttribute('decimal', '.');
+				//$coordinatesNode->setAttribute('cs', ' ');
+				//$coordinatesNode->setAttribute('ts', ',');
+				$xmlWriter->text($gmlCoordinatesFromWkt);
+			$xmlWriter->endElement();// gml:coordinates
+		} else if ('LineString' === $gmlType) {
+			$xmlWriter->startElement("gml:coordinates");
+				$xmlWriter->writeAttribute('decimal', '.');
+				$xmlWriter->writeAttribute('cs', ' ');
+				$xmlWriter->writeAttribute('ts', ',');
+				$xmlWriter->text($gmlCoordinatesFromWkt);
+			$xmlWriter->endElement();// gml:coordinates
+		} else {
+			$xmlWriter->startElement('gml:outerBoundaryIs');
+				$xmlWriter->startElement('gml:LinearRing');
+					$xmlWriter->startElement('gml:coordinates');
+						$xmlWriter->writeAttribute('decimal', '.');
+						$xmlWriter->writeAttribute('cs', ' ');
+						$xmlWriter->writeAttribute('ts', ',');
+						$xmlWriter->text($gmlCoordinatesFromWkt);
+					$xmlWriter->endElement();// gml:coordinates
+				$xmlWriter->endElement();// gml:LinearRing
+			$xmlWriter->endElement();// gml:outerBoundaryIs
+		}
+		$xmlWriter->endElement();// gml:{$gmlType}
+	}
+
 }

+ 35 - 0
SE/se-lib/Api/WfsLogger.php

@@ -0,0 +1,35 @@
+<?php
+
+class Api_WfsLogger {
+
+	protected $_reqId;
+	protected $_logFile;
+
+	public function __construct() {
+		$this->_logFile = "/tmp/wfs.log";
+		if (!is_writable($this->_logFile)) {
+			$fp = @fopen($this->_logFile, "w");
+			if ($fp === false) return;
+			@fclose($fp);
+		}
+		//unlink($this->_logFile);// TODO: DBG clear log
+		$this->_reqId = substr(uniqid(), 7);
+	}
+
+	public function reqDBG($request) {
+		$reqLog = "{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}";
+		if ($_SERVER['REQUEST_METHOD'] == 'POST') $reqLog .= "\n------------ POST:\n" . Request::getRequestBody();
+		if (!empty($request)) $reqLog .= "\n------------ request: " . json_encode($request);
+		$this->DBG($reqLog);
+	}
+
+	public function DBG($reqLog, $lineNr = null, $funName = null, $className = null) {
+		$codePos = ($lineNr)? $lineNr : '';
+		if (null !== $funName && null !== $className) $codePos = "{$className}::{$funName}():{$lineNr}";
+		if (null !== $funName && null === $className) $codePos = "{$funName}():{$lineNr}";
+		if ($codePos) $codePos = "\t{$codePos}";
+		$logMsg = date("Y-m-d H:i:s") . "\tReq:{$this->_reqId}{$codePos}\t{$reqLog}\n";
+		error_log($logMsg, 3, $this->_logFile);
+	}
+
+}

+ 10 - 6
SE/se-lib/Api/WfsQgis.php

@@ -5,6 +5,7 @@ Lib::loadClass('ApiDataSourceTodo');// TODO: @see Entity/Source/Mysql from featu
 Lib::loadClass('Api_WfsException');
 Lib::loadClass('Api_WfsQgisServer');
 Lib::loadClass('UserAcl');
+Lib::loadClass('Api_WfsLogger');
 
 class Api_WfsQgis extends ApiRouteBase {// TODO: extends Api_WfsBase which extends ApiBase
 
@@ -16,7 +17,9 @@ class Api_WfsQgis extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 
 	public function execute($request) {
 
-		$this->reqDBG($request, __LINE__);
+		$wfsLogger = new Api_WfsLogger();
+		$this->setLogger($wfsLogger);
+		$this->reqDBG($request);
 
 		/* TODO: return response xml document
 		$responseDocument = null;
@@ -34,7 +37,7 @@ class Api_WfsQgis extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 			throw new HttpException("Bad Request", 400);
 		} else {
 			$this->_dataSourceName = array_shift($request->segments);
-			$this->DBG("dataSourceAction({$this->_dataSourceName}) ...", __LINE__);
+			$this->DBG("_dataSourceName:$this->_dataSourceName", __LINE__, __FUNCTION__, __CLASS__);
 			try {
 				$this->dataSourceAction($request);
 			} catch (Api_WfsException $e) {
@@ -46,7 +49,7 @@ class Api_WfsQgis extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 				header('Content-type: application/xml');
 				echo $responseDocument;
 			}
-			$this->DBG("dataSourceAction() END", __LINE__);
+			$this->DBG("END", __LINE__, __FUNCTION__, __CLASS__);
 		}
 
 		exit;
@@ -81,8 +84,9 @@ class Api_WfsQgis extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 		$userAcl->fetchAllPerms(true);
 		DBG::_('DBG', '>2', 'userAcl', $userAcl, __CLASS__, __FUNCTION__, __LINE__);
 
-		$this->DBG("WfsServer(" . $this->_apiUser->getID() . ") ...", __LINE__);
+		$this->DBG("usr:" . $this->_apiUser->getID(), __LINE__, __FUNCTION__, __CLASS__);
 		$wfsServer = new Api_WfsQgisServer($userAcl);
+		$wfsServer->setLogger($this->_logger);
 		$wfsServer->setBaseUri($this->_apiBaseUri);
 		DBG::_('DBG', true, 'getBaseNamespaceUri:', $wfsServer->getBaseNamespaceUri(), __CLASS__, __FUNCTION__, __LINE__);
 		if ('WFS' != V::get('SERVICE', '', $request->query) and ('WFS' != V::get('service', '', $request->query)))  {
@@ -94,11 +98,11 @@ class Api_WfsQgis extends ApiRouteBase {// TODO: extends Api_WfsBase which exten
 			if (!method_exists($wfsServer, $methodName)) {
 				throw new Api_WfsException("Not Implemented " . htmlspecialchars($req), 501);
 			}
-			$this->DBG("WfsServer->{$methodName}() ...", __LINE__);
+			$this->DBG("execute: WfsServer->{$methodName}(\$urlQuery)", __LINE__);
 			$document = $wfsServer->$methodName($urlQuery);
 		}
 		else {
-			$this->DBG("WfsServer->parseXMLRequest() ...", __LINE__);
+			$this->DBG("execute: WfsServer->parseXMLRequest()", __LINE__);
 			$document = $wfsServer->parseXMLRequest();
 
 			header('Content-type: application/xml');

+ 99 - 9
SE/se-lib/Api/WfsQgisServer.php

@@ -46,11 +46,8 @@ class Api_WfsQgisServer extends Api_WfsServerBase {
 		$typeEx = explode(':', $type);
 		$maxFeatures = V::get('MAXFEATURES', '10000', $_REQUEST, 'int');// TODO: Set Deafult Limit
 		$srsname = V::get('SRSNAME', '', $_REQUEST);// eg. EPSG:4326
-		if (count($typeEx) == 2) {
-			return $this->getFeatures($typeEx[0], $typeEx[1], $maxFeatures, $srsname);
-		} else {
-			throw new HttpException("Wrong param TYPENAME", 400);
-		}
+		if (count($typeEx) != 2) throw new HttpException("Wrong param TYPENAME", 400);
+		return $this->getFeatures($typeEx[0], $typeEx[1], $maxFeatures, $srsname);
 	}
 
 	public function getFeatures($nsPrefix, $type, $maxFeatures, $srsname) {
@@ -83,11 +80,102 @@ class Api_WfsQgisServer extends Api_WfsServerBase {
 		$searchParams['limit'] = $maxFeatures;
 		$searchParams['order_by'] = $acl->getPrimaryKeyField();
 		$searchParams['order_dir'] = 'DESC';
-		if ($geomFld) $searchParams["f_{$geomFld}"] = 'IS NOT NULL';
-		if ($geomFld) $searchParams["f_{$geomFld}"] = 'GeometryType=' . strtoupper($dbGeomType);
-		//if ($geomFld) $searchParams["f_{$geomFld}"] = 'GeometryType=LINESTRING';
+		//if ($geomFld) $searchParams["f_{$geomFld}"] = 'IS NOT NULL';
+		//if ($geomFld) $searchParams["f_{$geomFld}"] = 'GeometryType=' . strtoupper($dbGeomType);
+		$geomType = strtoupper($dbGeomType);
+		if ($geomFld) $searchParams["ogc:Filter"] = <<<OGC_FILTER
+<ogc:Filter>
+	<ogc:Or>
+		<ogc:Not>
+			<ogc:PropertyIsNull>
+				<ogc:PropertyName>{$geomFld}</ogc:PropertyName>
+			</ogc:PropertyIsNull>
+		</ogc:Not>
+		<ogc:PropertyIsEqualTo>
+			<ogc:Function name="GeometryType">
+				 <ogc:PropertyName>{$geomFld}</ogc:PropertyName>
+			</ogc:Function>
+			<ogc:Literal>{$geomType}</ogc:Literal>
+		</ogc:PropertyIsEqualTo>
+	</ogc:Or>
+</ogc:Filter>
+OGC_FILTER;
 if($DBG){echo 'getItems:';print_r($searchParams);echo "\n";}
+		$this->DBG("getItems:" . json_encode($searchParams), __LINE__, __FUNCTION__, __CLASS__);
 		$items = $acl->getItems($searchParams);
+		$this->DBG("items(" . count($items) . ")", __LINE__, __FUNCTION__, __CLASS__);
+
+if (true) {//1 == V::get('XML_WRITER', 0, $_GET, 'int')) {
+		header('Content-type: application/xml; charset=utf-8');
+		$xmlWriter = new XMLWriter();
+		$xmlWriter->openUri('php://output');
+		$xmlWriter->setIndent(true);
+		if (!$xmlWriter) throw new HttpException("Error no XMLWriter", 404);
+		$xmlWriter->startDocument('1.0','UTF-8');
+		//$xmlWriter->startElementNS('wfs', 'FeatureCollection', 'http://www.opengis.net/wfs');
+		$xmlWriter->startElement('wfs:FeatureCollection');
+//		$xmlWriter->writeAttributeNS('xmlns', 'wfs', 'http://www.w3.org/2000/xmlns/', 'http://www.opengis.net/wfs');
+		$xmlWriter->writeAttribute('xmlns:wfs', 'http://www.opengis.net/wfs');
+		$xmlWriter->writeAttribute('xmlns', 'http://www.opengis.net/wfs');
+//		$xmlWriter->writeAttributeNS('xmlns', 'gml', 'http://www.w3.org/2000/xmlns/', 'http://www.opengis.net/gml');
+//		$xmlWriter->writeAttributeNS('xmlns', 'xsi', 'http://www.w3.org/2000/xmlns/', 'http://www.w3.org/2001/XMLSchema-instance');
+//		$xmlWriter->writeAttributeNS('xmlns', $wfsNs, 'http://www.w3.org/2000/xmlns/', $wfsNsUri);
+		$xmlWriter->writeAttribute('xmlns:gml', 'http://www.opengis.net/gml');
+		$xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+		$xmlWriter->writeAttribute("xmlns:{$wfsNs}", $wfsNsUri);
+		$xmlWriter->writeAttribute('xsi:schemaLocation', "{$wfsNsUri} {$featureTypeUri}");
+
+if($DBG){echo '(geomFld: '.$geomFld.'):';print_r($acl->getFieldType($geomFld));echo "\n";}
+		if (empty($items)) {
+			$pKeyField = $acl->getPrimaryKeyField();
+			$fakeItem = new stdClass();
+			$fakeItem->{$pKeyField} = 0;
+			if ('polygon' == $dbGeomType) {
+				$fakeItem->the_geom = "POLYGON(())";
+			} else if ('linestring' == $dbGeomType) {
+				$fakeItem->the_geom = "LINESTRING()";
+			} else if ('point' == $dbGeomType) {
+				$fakeItem->the_geom = "POINT(0,0)";
+			}
+			$items[0] = $fakeItem;
+		}
+		$dbgLoop = 0;
+		foreach ($items as $itemKey => $item) {
+			if (0 == (++$dbgLoop) % 1000) $this->DBG("items loop:{$dbgLoop}", __LINE__, __FUNCTION__, __CLASS__);
+if($DBG){echo 'item['.$itemKey.'] ('.$geomFld.')isEmpty('.empty($item->{$geomFld}).'):';print_r($item->{$geomFld});echo "\n";}
+			if ($geomFld) {
+				if (empty($item->{$geomFld})) {
+					continue;// QGIS crash when WFS contain features with empty geom field
+				}
+			}
+			$xmlWriter->startElement('gml:featureMember');
+				$xmlWriter->startElement("{$wfsNs}:{$type}");
+					$xmlWriter->writeAttribute('fid', "{$type}.{$itemKey}");
+					foreach ($fldList as $fldName) {
+						if ($acl->isGeomField($fldName)) {
+							$xmlWriter->startElement("{$wfsNs}:{$fldName}");
+								$this->_typeConverter->createGmlFromWkt_xmlWriter($item->{$fldName}, $xmlWriter);
+							$xmlWriter->endElement();// {$wfsNs}:{$fldName}
+						} else {
+							$value = str_replace('&', '&amp;', $item->{$fldName});
+							if (empty($value) && '0' !== $value) {
+								continue;
+							} else {
+								$xmlWriter->startElement("{$wfsNs}:{$fldName}");
+								$xmlWriter->text($value);
+								$xmlWriter->endElement();// {$wfsNs}:{$fldName}
+							}
+						}
+					}
+				$xmlWriter->endElement();// {$wfsNs}:{$type}
+			$xmlWriter->endElement();// gml:featureMember
+		}
+		$xmlWriter->endElement();// wfs:FeatureCollection
+		$xmlWriter->endDocument();
+		$this->DBG("items loop END", __LINE__, __FUNCTION__, __CLASS__);
+		//'<!-- .EOF -->';
+		exit;
+}
 
 		$dom = new DOMDocument('1.0', 'utf-8');
 		$dom->formatOutput = true;
@@ -130,8 +218,9 @@ if($DBG){echo '(geomFld: '.$geomFld.'):';print_r($acl->getFieldType($geomFld));e
 			}
 			$items[0] = $fakeItem;
 		}
+		$dbgLoop = 0;
 		foreach ($items as $itemKey => $item) {
-
+			$dbgLoop++; if (0 == $dbgLoop % 100) $this->DBG("items loop:{$dbgLoop}", __LINE__, __FUNCTION__, __CLASS__);
 if($DBG){echo 'item['.$itemKey.'] ('.$geomFld.')isEmpty('.empty($item->{$geomFld}).'):';print_r($item->{$geomFld});echo "\n";}
 			if ($geomFld) {
 				if (empty($item->{$geomFld})) {
@@ -173,6 +262,7 @@ if(0){// TODO: get BBOX
 						$featureNode->appendChild($featureFldNode);
 					}
 		}
+		$this->DBG("items loop END", __LINE__, __FUNCTION__, __CLASS__);
 
 		return $dom->saveXml();
 	}

+ 12 - 3
SE/se-lib/Api/WfsServerBase.php

@@ -8,6 +8,7 @@ class Api_WfsServerBase {
 	public $_usrAcl;
 	public $_typeConverter;
 	public $_apiBaseUri;
+	protected $_logFile;
 
 	public function __construct($usrAcl) {
 		$this->_usrAcl = $usrAcl;
@@ -1249,9 +1250,8 @@ if($DBG){echo '$validateConvertedTransactionXsdString:';print_r($validateConvert
 
 	// @param $typeNames = array( array( $nsPrefix, $type ) )
 	public function _getDescribeFeatureTypes($typeNames, $simple = true) {
-		if (empty($typeNames)) {
-			throw new HttpException("Feature Type Names not defined", 400);
-		}
+		if (empty($typeNames)) throw new HttpException("Feature Type Names not defined", 400);
+		$this->DBG("types:" . json_encode($typeNames), __LINE__, __FUNCTION__, __CLASS__);
 		$baseNsUri = $this->getBaseNamespaceUri();
 
 		$rootWfsNs = 'p5';
@@ -1435,4 +1435,13 @@ if($DBG){echo '$validateConvertedTransactionXsdString:';print_r($validateConvert
 		return $fldList;
 	}
 
+	public function setLogger($logger) {
+		$this->_logger = $logger;
+	}
+
+	public function DBG($reqLog, $lineNr = null, $funName = null, $className = null) {
+		if (!$this->_logger) return;
+		$this->_logger->DBG($reqLog, $lineNr, $funName, $className);
+	}
+
 }