skip to Main Content

I try to find all elements <coordinates> into kml file, but not all of them have the same structure

for example:

<Placemark>
    <Polygon>
        <outerBoundaryIs>
            <LinearRing>
                <coordinates>
                    -103.6705130315019, 18.9861834531002, 312.462181927998 
                    -103.5618913951496, 18.98673753736649, 827.0547547230755
                    -103.6101814498474, 19.21464825463783, 601.556189231858
                    -103.6705130315019, 18.9861834531002, 312.462181927998 
                </coordinates>
            </LinearRing>
        </outerBoundaryIs>
    </Polygon>
</Placemark>

And others files have other structure, for example:

<Placemark>
    <MultiGeometry>
        <Polygon>
            <outerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -104.085929389248,19.3541278793555, 0 
                        -104.085635763744,19.3536293022551, 0
                        -104.087174259165,19.3527060222406, 0
                        -104.087310816944,19.3536755662883, 0
                        -104.085929389248,19.3541278793555,0 
                    </coordinates>
                </LinearRing>
            </outerBoundaryIs>
        </Polygon>
    </MultiGeometry>
</Placemark>

I need to get all coordinates ignoring parent elements, also, some file, have one o more coordinates elements

The next code runs, but only get me one element,

foreach( $xml->getDocNamespaces(TRUE) as $strPrefix => $strNamespace ) {
    if(strlen($strPrefix)==0) {
        $strPrefix="a"; //Assign an arbitrary namespace prefix.
    }
    $xml->registerXPathNamespace($strPrefix,$strNamespace);
}

$pieces = explode(" ", $xml->xpath("//a:coordinates")[0]);
foreach ($pieces as $coordinates) {
    $args     = explode(",", $coordinates);
    if (strlen($args[1]) != 0 ){
        $coordenadas .= '{"lat": ' . $args[1] . ', "lng": ' . $args[0] . '},';
    }
}

If the file have other coordinates elements, I cant get it.

2

Answers


  1. Rather than using SimpleXML & it’s associated classes/methods ( which I have never used so can offer no guidance ) the task of finding all the coordinates in a KML file can be done quite easily using the native DOMDocument and DOMXPath

    # This source file has multiple separate Placemarks, each with many coordinates
    # and has several different associated namespaces.
    $file='/files/kml/cta.kml';
    $output=array();
    
    # load the XML/KML file
    libxml_use_internal_errors( true );
    $dom=new DOMDocument;
    $dom->validateOnParse=false;
    $dom->strictErrorChecking=false;
    $dom->recover=true;
    $dom->load( $file );
    libxml_clear_errors();
    
    # load XPath and register default namespace
    $xp=new DOMXPath( $dom );
    
    # Find & register all namespaces
    $expr='namespace::*';
    $col=$xp->query( $expr );
    if( $col && $col->length > 0 ){
        foreach( $col as $index => $node ){
            $xp->registerNameSpace( $node->localName, $node->nodeValue );
            # set a default... 
            $def=$node->localName;
        }
    }
    
    # choose the default namespace and create the basic query
    # In **this** file the prefix is not important, this may not always be the case!
    $expr=sprintf('//%s:coordinates', $def );
    
    
    #Query the document to find matching nodes.
    $col=$xp->query( $expr );
    if( $col && $col->length > 0 ){
        foreach( $col as $node ){
            # each found node has a long list of coordinates/altitudes - create
            # an array by exploding on new line character.
            $lines=explode( PHP_EOL, $node->nodeValue );
            
            
            
            #iterate through found lines of coordinates and split into constituent pieces.
            foreach( $lines as $line ){
                # remove tabs and other control characters
                $line=preg_replace('@[tr]@', '', $line );
                
                # split the line at suitable point
                $line=preg_split( '@n@', $line );
                
                # remove empty items
                $line=array_filter( $line );
                
                foreach( $line as $coordinate ){
                    # does each coordinate have an altitude or not?
                    $count=substr_count( $coordinate, ',' );
                    
                    if( $count==1 ){
                        
                        # Only Long & Lat per coordinate
                        list( $lng, $lat )=explode(',', $coordinate );
                        $output[]=array( 'lat'=>trim($lat), 'lng'=>trim($lng), 'Altitude'=>0 );
                        
                    } elseif( $count==2 ) {
                        
                        # Long, Lat & Altitude per coordinate
                        list( $lng, $lat, $alt )=explode(',', $coordinate );
                        $output[]=array( 'lat'=>trim($lat), 'lng'=>trim($lng), 'Altitude'=>trim($alt) );
                    }
                }
            }
        }
    }
    
    printf('<pre>%s</pre>',print_r($output,true));
    

    The output from this is of the form:

    Array
    (
        [0] => Array
            (
                [lat] => 41.97881025520548
                [lng] => -87.89289951324463
                [Altitude] => 0
            )
        [1] => Array
            (
                [lat] => 41.97788506340239
                [lng] => -87.89184808731079
                [Altitude] => 0
            )
        [2] => Array
            (
                [lat] => 41.97762983571196
                [lng] => -87.89150476455688
                [Altitude] => 0
            )
    

    I verified that this returns all coordinates with some basic counting of strings in results and original

    Login or Signup to reply.
  2. Do not try to determinate the namespaces from prefixes in the XML document. The unique identifier of a namespace is the URI. The prefix registration is for readability. Any element node can contain namespace definitions. Namespace prefixes can change and are optional for element nodes.

    The parser resolves the node name into a local name and a namespace URI. The following 3 examples can all be read as {http://www.opengis.net/kml/2.2}kml (Clark notation ).

    • <kml xmlns="http://www.opengis.net/kml/2.2">
    • <k:kml xmlns:k="http://www.opengis.net/kml/2.2">
    • <keyhole:kml xmlns:keyhole="http://www.opengis.net/kml/2.2">

    So just define and register your own prefix for the known namespace URI.

    Basic Example:

    
    const XMLNS_KML = "http://www.opengis.net/kml/2.2";
    
    $kml = new SimpleXMLElement(getKMLString());
    $kml->registerXpathNamespace('k', XMLNS_KML);
    
    $coordinates = [];
    foreach ($kml->xpath('//k:Placemark//k:coordinates') as $coordinates) {
        var_dump(trim($coordinates));
    }
    
    
    function getKMLString(): string {
      return <<<'XML'
    <kml xmlns="http://www.opengis.net/kml/2.2">
    <Placemark>
        <Polygon>
            <outerBoundaryIs>
                <LinearRing>
                    <coordinates>
                        -103.6705130315019, 18.9861834531002, 312.462181927998 
                        -103.5618913951496, 18.98673753736649, 827.0547547230755
                        -103.6101814498474, 19.21464825463783, 601.556189231858
                        -103.6705130315019, 18.9861834531002, 312.462181927998 
                    </coordinates>
                </LinearRing>
            </outerBoundaryIs>
        </Polygon>
    </Placemark>
    <Placemark>
        <MultiGeometry>
            <Polygon>
                <outerBoundaryIs>
                    <LinearRing>
                        <coordinates>
                            -104.085929389248,19.3541278793555, 0 
                            -104.085635763744,19.3536293022551, 0
                            -104.087174259165,19.3527060222406, 0
                            -104.087310816944,19.3536755662883, 0
                            -104.085929389248,19.3541278793555,0 
                        </coordinates>
                    </LinearRing>
                </outerBoundaryIs>
            </Polygon>
        </MultiGeometry>
    </Placemark>
    </kml>
    XML;
    }
    

    Output:

    string(283) "-103.6705130315019, 18.9861834531002, 312.462181927998 
                        -103.5618913951496, 18.98673753736649, 827.0547547230755
                        -103.6101814498474, 19.21464825463783, 601.556189231858
                        -103.6705130315019, 18.9861834531002, 312.462181927998"
    string(285) "-104.085929389248,19.3541278793555, 0 
                            -104.085635763744,19.3536293022551, 0
                            -104.087174259165,19.3527060222406, 0
                            -104.087310816944,19.3536755662883, 0
                            -104.085929389248,19.3541278793555,0"
    

    IMPORTANT! if you use Xpath expressions on the returned SimpleXMLElement instances you will need to register on each object again.

    With DOM the bootstrap is slightly more but you have an explicit $xpath object with the namespace registration. Also DOMXpath::evaluate() supports expressions that can return scalar values directly. Also it can use callbacks from XPath into PHP.

    $document = new DOMDocument();
    $document->loadXML(getKMLString());
    $xpath = new DOMXPath($document);
    $xpath->registerNamespace('k', XMLNS_KML);
    
    $coordinates = [];
    foreach ($xpath->evaluate('//k:Placemark//k:coordinates') as $coordinates) {
        var_dump(trim($coordinates->textContent));
    }
    

    The placemark coordinates can be just a point, a simple shape or complex shapes – see https://developers.google.com/kml/documentation/kml_tut#placemarks.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search