registerNodeClass('DOMElement', 'WrMapDOMElement'); } /// Creates and adds the element with the given tag name and returns it. /// Additionally, it calls setAttribute($key, $value) for every entry /// in $attributes. function appendElement($tagName, $attributes=array()) { $child = $this->appendChild($this->createElement($tagName)); foreach ($attributes as $key => $value) $child->setAttribute($key, $value); return $child; } } class WrMapDOMElement extends DOMElement { /// Creates and adds the element with the given tag name and returns it /// Additionally, it calls setAttribute($key, $value) for every entry /// in $attributes. function appendElement($tagName, $attributes=array()) { $child = $this->appendChild($this->ownerDocument->createElement($tagName)); foreach ($attributes as $key => $value) $child->setAttribute($key, $value); return $child; } /// Adds any UTF-8 string as content of the element - it will be escaped. function appendText($text) { return $this->appendChild($this->ownerDocument->createTextNode($text)); } // Appends a CDATASections to the element. This can be used to include // raw (unparsed) HTML to the DOM tree as it is necessary because // $parser->recursiveTagParse does not always escape & characters. // (see https://bugzilla.wikimedia.org/show_bug.cgi?id=55526 ) // Workaround: Use a CDATA section. When serializing with $doc->saveHTML, // the is returned as ... . // However, we end up having unescaped & in the output due to this bug in recursiveTagParse. function appendCDATA($data) { return $this->appendChild($this->ownerDocument->createCDATASection($data)); } } // WrBaseMap // --------- class WrBaseMap { // gets coordinates and returns an array of lon/lat coordinate pairs, e.g. // 47.12 N 11.87 E // 47.13 N 11.70 E // -> // array(array(11.87, 47.12), array(11.70, 47.13)) public static function geo_to_coordinates($input) { $matches = array(); $num_matches = preg_match_all('/\s*(\d+\.?\d*)\s*N?\s+(\d+\.?\d*)\s*E?\s*/', $input, $matches); $result = array(); for ($i=0; $i!=$num_matches; ++$i) { $result[] = array(floatval($matches[2][$i]), floatval($matches[1][$i])); } if (implode($matches[0]) != $input) throw new Exception(wfMessage('wrmap-error-coordinate-format', $input)->text()); return $result; } /// Takes a page title from the wiki and returns an image (if available) /// or Null. For image wiki pages, the image is the corresponding image, /// for inns it's the image of the "Gasthausbox". public static function wikipage_to_image($title, $width) { $file = false; // File class or false // for NS_FILE titles, use the corresponding file as image if ($title->getNamespace() == NS_FILE) { $file = wfFindFile($title); // $file is a mediawiki File class or false } else { $categories = $title->getParentCategories(); // e.g. array('Kategorie:Rodelbahn' => 'Juifenalm') global $wgContLang; $key_sledrun = $wgContLang->getNSText(NS_CATEGORY) . ':Rodelbahn'; if (array_key_exists($key_sledrun, $categories)) { // for sledrun titles use the image from the rodelbahnbox $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('wrsledruncache', 'image', array('page_id' => $title->getArticleID()), __METHOD__); $image = $dbr->fetchRow($res); if ($image && !is_null($image['image'])) $file = wfFindFile($image['image']); $dbr->freeResult($res); } $key_inn = $wgContLang->getNSText(NS_CATEGORY) . ':Gasthaus'; if (array_key_exists($key_inn, $categories)) { // for inn titles use the image from the gasthausbox $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('wrinncache', 'image', array('page_id' => $title->getArticleID()), __METHOD__); $image = $dbr->fetchRow($res); if ($image && !is_null($image['image'])) $file = wfFindFile($image['image']); $dbr->freeResult($res); } } if ($file === false) return Null; if (!$file->canRender()) return Null; $thumb_url = $file->createThumb($width, $width); // limit width and hight to $width if (strlen($thumb_url) == 0) return Null; return $thumb_url; } // convert sledruns to geojson (http://www.geojson.org/geojson-spec.html) // Returns an array of features public static function sledruns_to_json_features() { $json_features = array(); // result $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select(array('wrsledruncache', 'wrreportcache'), array('wrsledruncache.page_title', 'position_latitude', 'position_longitude', 'date_report', '`condition`'), array('show_in_overview', 'not under_construction'), __METHOD__, array(), array('wrreportcache' => array('left outer join', 'wrsledruncache.page_id=wrreportcache.page_id'))); while ($sledrun = $dbr->fetchRow($res)) { $lat = $sledrun['position_latitude']; $lon = $sledrun['position_longitude']; if (is_null($lat) || is_null($lon)) continue; $lat = floatval($lat); $lon = floatval($lon); $title = Title::newFromText($sledrun['page_title']); $properties = array('type' => 'sledrun', 'name' => $title->getText(), 'wiki' => $title->getLocalUrl()); if (!is_null($sledrun['date_report'])) $properties['date_report'] = $sledrun['date_report']; if (!is_null($sledrun['condition'])) $properties['condition'] = intval($sledrun['condition']); $image_url = WrBaseMap::wikipage_to_image($title, 150); if (!is_null($image_url)) $properties['thumb_url'] = $image_url; $json_feature = array( 'type' => 'feature', 'geometry' => array( 'type' => 'Point', 'coordinates' => array($lon, $lat) ), 'properties' => $properties ); $json_features[] = $json_feature; } $dbr->freeResult($res); return $json_features; } // convert XML to geojson (http://www.geojson.org/geojson-spec.html) // Returns an array of features public static function xml_to_json_features($input) { libxml_use_internal_errors(true); // without that, we get PHP Warnings if the $input is not well-formed $xml = new SimpleXMLElement($input); // input $whitespace = (string) $xml; // everything between and that's not a sub-element if (strlen($whitespace) > 0 && !ctype_space($whitespace)) { // there must not be anythin except sub-elements or whitespace throw new Exception(wfMessage('wrmap-error-invalid-text', trim($xml))->text()); } $json_features = array(); // output $point_types = array('gasthaus', 'haltestelle', 'parkplatz', 'achtung', 'foto', 'verleih', 'punkt'); $line_types = array('rodelbahn', 'gehweg', 'alternative', 'lift', 'anfahrt', 'linie'); foreach ($xml as $feature) { $given_properties = array(); foreach ($feature->attributes() as $key => $value) $given_properties[] = $key; // determine feature type $is_point = in_array($feature->getName(), $point_types); $is_line = in_array($feature->getName(), $line_types); if (!$is_point && !$is_line) { throw new Exception(wfMessage('wrmap-error-invalid-element', $feature->getName(), '<' . implode('>, <', array_merge($point_types, $line_types)) . '>')->text()); } // point if ($is_point) { $properties = array('type' => $feature->getName()); $allowed_properties = array('name', 'wiki'); $wrong_properties = array_diff($given_properties, $allowed_properties); if (count($wrong_properties) > 0) throw new Exception(wfMessage('wrmap-error-invalid-attribute', reset($wrong_properties), $feature->getName(), "'" . implode("', '", $allowed_properties) . "'")->text()); foreach ($given_properties as $property) { $propval = (string) $feature[$property]; if ($property == 'wiki') { $title = Title::newFromText($propval); $propval = $title->getLocalUrl(); $file_url = WrBaseMap::wikipage_to_image($title, 200); if (!is_null($file_url)) $properties['thumb_url'] = $file_url; } $properties[$property] = $propval; } $coordinates = WrBaseMap::geo_to_coordinates($feature); if (count($coordinates) != 1) throw new Exception(wfMessage('wrmap-error-coordinate-count', $feature->getName())->text()); $json_feature = array( 'type' => 'feature', 'geometry' => array( 'type' => 'Point', 'coordinates' => reset($coordinates) ), 'properties' => $properties ); $json_features[] = $json_feature; } // line if ($is_line) { $properties = array('type' => $feature->getName()); $allowed_properties = array('farbe', 'dicke'); $wrong_properties = array_diff($given_properties, $allowed_properties); if (count($wrong_properties) > 0) throw new Exception(wfMessage('wrmap-error-invalid-attribute', reset($wrong_properties), $feature->getName(), "'" . implode("', '", $allowed_properties) . "'")->text()); if (isset($feature['farbe'])) { $color = (string) $feature['farbe']; // e.g. #a200b7 if (preg_match('/^#[0-9a-f]{6}$/i', $color) != 1) throw new Exception(wfMessage('wrmap-error-line-color')->text()); $properties['strokeColor'] = $color; } if (isset($feature['dicke'])) { $stroke_width = (int) $feature['dicke']; // e.g. 6 if (((string) $stroke_width) !== (string) $feature['dicke']) throw new Exception(wfMessage('wrmap-error-line-width')->text()); $properties['strokeWidth'] = $stroke_width; } $json_feature = array( 'type' => 'feature', 'geometry' => array( 'type' => 'LineString', 'coordinates' => WrBaseMap::geo_to_coordinates($feature) ), 'properties' => $properties ); $json_features[] = $json_feature; } } return $json_features; } /// Renders the tag and the tag. /// The WrBaseMap class would be the only class needed but as the function render() does not provide an argument /// telling which tag name called the function, a trick with two inherited classes has to be used. /// @param $content string - the content of the tag /// @param $args array - the array of attribute name/value pairs for the tag /// @param $parser Parser - the MW Parser object for the current page /// /// @return string - the html for rendering the map public static function render($content, $args, $parser, $frame) { // Unfortunately, $tagname is no argument of this function, therefore we have to use a trick with derived classes. $tagname = strtolower(get_called_class()); // either wrmap or wrgmap assert(in_array($tagname, array('wrmap', 'wrgmap'))); $parserOutput = $parser->getOutput(); // $parserOutput->addHeadItem('', 'googlemaps'); $parserOutput->addModules('ext.wrmap'); // append all sledruns as icon $json_features = array(); $show_sledruns = ($tagname == 'wrgmap'); if ($show_sledruns) { $json_features = array_merge($json_features, WrBaseMap::sledruns_to_json_features()); } try { // map properties $properties = array(); if (isset($args['lat'])) $properties['lat'] = (float) $args['lat']; // latitude as float value if (isset($args['lon'])) $properties['lon'] = (float) $args['lon']; // longitude as float value if (isset($args['zoom'])) $properties['zoom'] = (int) $args['zoom']; // zoom as int value if (isset($args['width'])) $properties['width'] = (int) $args['width']; // width as int value if (isset($args['height'])) $properties['height'] = (int) $args['height']; // height as int value // append all elements in the XML $json_features = array_merge($json_features, WrBaseMap::xml_to_json_features('' . $content . '')); } catch (Exception $e) { $doc = new WrMapDOMDocument(); $doc->appendElement('div', array('class' => 'error'))->appendText('Fehler beim Parsen der Landkarte: ' . $e->getMessage()); return array($doc->saveHTML($doc->firstChild), 'markerType' => 'nowiki'); } // create final geojson $json = array( 'type' => 'FeatureCollection', 'features' => $json_features, 'properties' => $properties ); $json_string = json_encode($json); // Create
element where the map is placed in global $wgExtensionAssetsPath; $doc = new WrMapDOMDocument(); $div = $doc->appendElement('div', array('class' => 'wrmap', 'style' => 'border-style:none;', 'data-ext-path' => "$wgExtensionAssetsPath/wrmap")); // progress message $div->appendElement('div', array())->appendText(wfMessage('wrmap-loading')->text()); // data $div->appendElement('div', array('style' => 'height: 0px; display:none;'))->appendText($json_string); return array($doc->saveHTML($div), 'markerType' => 'nowiki'); } public static function onEnableMobileModules($out, $mode) { $out->addModules('ext.wrmap.mobile'); return true; } } // tag class WrMap extends WrBaseMap { } // tag class WrGMap extends WrBaseMap { } ?>