getAttribute('transform')) { if(preg_match_all( "/(\w+)\s*\(\s*(-?\d+.?\d*)\s*(?:,\s*(-?\d+.?\d*))?\s*\)/U", $t, $matches, PREG_SET_ORDER)) { foreach ($matches as $m) { switch($m[1]) { case "translate": $r[0] += $m[2]; $r[1] += $m[3]; break; case "matrix": case "scale": case "rotate": case "skewX": case "skewY": default: trigger_error("Unsupported transform attribute: ".(string)$m[0], E_USER_ERROR); } } } else { // trigger_error("Unsupported transform attribute: ".(string)$t, // E_USER_ERROR); } } return $r; } /* Compute the coordinate system of a node. * Only translations are supported. viewBox attributes are ignored. * http://www.w3.org/TR/SVG/coords.html#EstablishingANewUserSpace */ private static function compose_transform($n) { $r = array(0., 0.); do { $t = self::get_transform($n); $r[0] += $t[0]; $r[1] += $t[1]; $n = $n->parentNode; } while($n->parentNode); return $r; } /* Convert an svg path 'd' attribute into an HTML coords attribute * $cs is the current coordinate system * http://www.w3.org/TR/SVG/paths.html#PathData * http://www.w3.org/TR/html40/struct/objects.html#adef-shape */ private static function convert_path($data, $cs, $ratio_w, $ratio_h) { /* This is an ad-hoc hack which works quite well if the path has * following form: * M x,y L x,y L x,y ... L x,y z * It does not handle the SVG spec, because PHP lacks proper parsing * tools. It does NOT handle relative coordinates either. * Bezier curves are converted to polygons following the control * points --- yes, this is VERY ugly. */ if (preg_match("/[^\sMCLz\d.,-]/",$data)) { trigger_error("Unsupported path data attribute: ". $data, E_USER_ERROR); return NULL; } $points = preg_split("/[\sCMLz]+/",$data, -1, PREG_SPLIT_NO_EMPTY); foreach($points as $k => $p) { $xy = preg_split("/,/", $p); if (count($xy) != 2) { trigger_error("Unsupported path data attribute: ". $data, E_USER_ERROR); return NULL; } $points[$k] = implode(",", array(($xy[0] + $cs[0]) * $ratio_w, ($xy[1] + $cs[1]) * $ratio_h)); } return (implode(",",$points)); } /* Get the min and max x and y coordinates of an svg path 'd' attribute * $cs is the current coordinate system * http://www.w3.org/TR/SVG/paths.html#PathData * http://www.w3.org/TR/html40/struct/objects.html#adef-shape */ private static function path_minmax($data, $cs) { /* Same limitations as convert_path */ if (preg_match("/[^\sMCLz\d.,-]/",$data)) { trigger_error("Unsupported path data attribute: ". $data, E_USER_ERROR); return NULL; } $points = preg_split("/[\sCMLz]+/",$data, -1, PREG_SPLIT_NO_EMPTY); foreach($points as $k => $p) { $xy = preg_split("/,/", $p); if (count($xy) != 2) { trigger_error("Unsupported path data attribute: ". $data, E_USER_ERROR); return NULL; } $x[$k] = $xy[0] + $cs[0]; $y[$k] = $xy[1] + $cs[1]; } return (array( "minx" => min($x), "miny" => min($y), "maxx" => max($x), "maxy" => max($y))); } /* Get the title of a given path node. */ private static function get_title($n) { $title = (string) $n->getElementsByTagName('title')->item(0)->textContent; $title .= " — ". (string) $n->getElementsByTagName('desc')->item(0)->textContent; return $title; } /* Compute the areas of an image map. * $dom is the svg DOM, $w and $h the width and height of the resulting * image - 0 if you want to extract this from the svg - and $regexp is a * selects the nodes to include in the image map (based on their id). * If only one of $w and $h is given, preserve the svg ratio. */ private static function compute_areas($dom, $w, $h, $regexp, $deptitle = 0) { $areas = ""; $svg = $dom->getElementsByTagName('svg')->item(0); $svg_w = (string) $svg->getAttribute('width'); $svg_h = (string) $svg->getAttribute('height'); if ($w == 0 && $h == 0) { $w = (int) $svg_w; $h = (int) $svg_h; } elseif ($w == 0) $w = (int) ($svg_w * $h / $svg_h); elseif ($h == 0) $h = (int) ($svg_h * $w / $svg_w); $ratio_w = $w / $svg_w; $ratio_h = $h / $svg_h; $paths = $dom->getElementsByTagName('path'); foreach ($paths as $path) if (preg_match($regexp, $path->getAttribute('id')) || preg_match($regexp, $path->getAttribute('class'))) { $cs = self::compose_transform($path); $points = self::convert_path($path->getAttribute('d'), $cs, $ratio_w, $ratio_h); if ($deptitle) { $title = $path->getAttribute('title'); $id = preg_replace('/d/', '', $path->getAttribute('id')); $href = url_for("@local-authority/view/$id"); } else { $id = $path->getAttribute('id'); $title = self::get_title($path); $href = url_for("@redirect_parlementaires_circo?code=".$path->getAttribute('id')); } $areas .= "\"".$title."\"\n"; } return array('areas' => $areas, 'w' => $w, 'h' => $h); } /* Crop an svg dom to keep only the $tags which fullfill the * $regexp condition. Any other path is removed, and the * image is cropped so as to focus on the remaining paths, with some * $margin. */ private static function crop_svg($dom, $regexp, $margin, $tags = array('path', 'text')) { $svg = $dom->getElementsByTagName('svg')->item(0); $toRemove = array(); $minx = array(); $maxx = array(); $miny = array(); $maxy = array(); foreach($tags as $tag) { $paths = $dom->getElementsByTagName($tag); foreach ($paths as $path) { if (preg_match($regexp, $path->getAttribute('id'))) { $cs = self::compose_transform($path); $t = self::path_minmax($path->getAttribute('d'), $cs); $minx[] = $t["minx"]; $maxx[] = $t["maxx"]; $miny[] = $t["miny"]; $maxy[] = $t["maxy"]; } else { /* WARNING You can't remove DOMNodes from a DOMNodeList as you're * iterating over them in a foreach loop. */ $toRemove[] = $path; } } } foreach($toRemove as $node) { $node->parentNode->removeChild($node); } if (!count($minx)) return; $x_min = min($minx) - $margin; $x_max = max($maxx) + $margin; $y_min = min($miny) - $margin; $y_max = max($maxy) + $margin; $svg->setAttribute('width', $x_max - $x_min); $svg->setAttribute('height', $y_max - $y_min); $svg->setAttribute('transform', "translate(".-$x_min.",".-$y_min.")"); } private static function generateSvgDep($map, $w, $h) { $mydom = new DOMDocument(); $mydom->preserveWhiteSpace = FALSE; // FIXME Use loadXML to load from a string instead (database) $mydom->load($map); return $mydom; } public static function echoDeptmtsMap($w, $h, $link = '') { $dom = self::generateSvgDep("france_regions.svg", $w, $h); $r = self::compute_areas($dom, $w, $h, '/^d\d+/', 1); $w = $r['w']; $h = $r['h']; $src = url_for("@map/render/departements/$w/$h"); if ($link) $out .= ''; $out .= "\"Carte'; if ($link) $out .= ''; $out .= ""; $out .= $r['areas']; $out .= ""; return $out; } public static function echoRegionsMap($w, $h, $link = '') { $dom = self::generateSvgDep("france_regions.svg", $w, $h); $r = self::compute_areas($dom, $w, $h, '/region/', 1); $w = $r['w']; $h = $r['h']; $src = url_for("@map/render/regions/$w/$h"); $out = ''; if ($link) $out .= ''; $out .= "\"Carte'; if ($link) $out .= ''; $out .= ""; $out .= $r['areas']; $out .= ""; return $out; } private static function echoDeptmtsImage($w, $h) { $dom = self::generateSvgDep('france_regions.svg',$w, $h); $im = new Imagick("france_regions.svg"); //$im->readImageBlob($dom->saveXML(), "france_deptmts.svg"); $res = $im->getImageResolution(); $x_ratio = $res['x'] / $im->getImageWidth(); $y_ratio = $res['y'] / $im->getImageHeight(); $im->removeImage(); $im->setSize($w, $h); //$im->setResolution($w * $x_ratio, $h * $y_ratio); //$im->readImageBlob($dom->saveXML()); $im->setResolution($w * $x_ratio, $h * $y_ratio); $im->readImage("france_regions.svg"); $im->setResolution($w * $x_ratio, $h * $y_ratio); $im->resizeImage ( $w, $h, imagick::FILTER_CUBIC, .6 ); $im->setImageFormat("png"); echo $im; } private static function echoRegionsImage($w, $h) { $dom = self::generateSvgDep('france_regions.svg',$w, $h); $im = new Imagick("france_regions.svg"); //$im->readImageBlob($dom->saveXML(), "france_regions.svg"); $res = $im->getImageResolution(); $x_ratio = $res['x'] / $im->getImageWidth(); $y_ratio = $res['y'] / $im->getImageHeight(); $im->removeImage(); $im->setResolution($w * $x_ratio, $h * $y_ratio); //$im->readImageBlob($dom->saveXML()); $im->readImage("france_regions.svg"); $im->setImageFormat("png"); $im->resizeImage ( $w, $h, imagick::FILTER_CUBIC, .6 ); echo $im; } private static function generateSvgDom($circo, $w, $h) { $dom = new DOMDocument(); $dom->preserveWhiteSpace = FALSE; // FIXME Use loadXML to load from a string instead (database) $dom->load("circo.svg"); if(preg_match("/^\d\d[\dab]$/",$circo)) self::crop_svg($dom, "/^$circo-\d\d$/", 10); return $dom; } private static function prepareMap($circo, $w, $h) { $dom = self::generateSvgDom($circo, $w, $h); if($circo == "full") $regexp = "/^\d\d[\dab]-(0[1-9]|[1-9]\d)$/"; else $regexp = "/^$circo-(0[1-9]|[1-9]\d)$/"; return array($dom, self::compute_areas($dom, $w, $h, $regexp)); } /* $circo is a three digits string, or "full" for the full map */ public static function echoCircoMap($circo, $w, $h) { $arr = self::prepareMap($circo, $w, $h); $r = $arr[1]; $w = $r['w']; $h = $r['h']; $src = url_for("@circo_image_png?circo=$circo&w=$w&h=$h"); echo "'; echo ""; echo $r['areas']; echo ""; } /* $circo is a three digits string, or "full" for the full map */ private static function echoCircoImage($circo, $w, $h) { /* If you want to resize a vector-graphics image (such as SVG) to a * certain dimension in pixels, without losing quality, you have to do * this * http://www.php.net/manual/en/function.imagick-setresolution.php */ $arr = self::prepareMap($circo, $w, $h); $dom = $arr[0]; $r = $arr[1]; $w = $r['w']; $h = $r['h']; $im = new Imagick(); $im->readImageBlob($dom->saveXML()); $res = $im->getImageResolution(); $x_ratio = $res['x'] / $im->getImageWidth(); $y_ratio = $res['y'] / $im->getImageHeight(); $im->removeImage(); $im->setResolution($w * $x_ratio, $h * $y_ratio); $im->readImageBlob($dom->saveXML()); $im->setImageFormat("png"); echo $im; } public function executeGetDeptmtsimagepng($w, $h) { header("Content-type: image/png"); self::echoDeptmtsImage($w, $h); return 0; } public function executeGetRegionsimagepng($w, $h) { header("Content-type: image/png"); self::echoRegionsImage($w, $h); return 0; } public function executeGetCircoimagepng(sfWebRequest $request) { $circo = $request->getParameter('circo'); $w = $request->getParameter('w'); $h = $request->getParameter('h'); header("Content-type: image/png"); self::echoCircoImage($circo, $w, $h); return 0; } public function executeList(sfWebRequest $request) { $this->circos = Parlementaire::$dptmt_nom; } public function executeShow(sfWebRequest $request) { $this->circo = preg_replace('/_/', ' ', $request->getParameter('departement')); $this->forward404Unless($this->circo); $this->departement_num = Parlementaire::getNumeroDepartement($this->circo); $this->parlementaires = Doctrine::getTable('Parlementaire')->createQuery('p') ->where('p.nom_circo = ?', $this->circo) ->addOrderBy('p.num_circo') ->execute(); $this->total = count($this->parlementaires); $this->forward404Unless($this->total); if ($this->total == 1) return $this->redirect('@parlementaire?slug='.$this->parlementaires[0]['slug']); } public function executeSearch(sfWebRequest $request) { $this->search = $request->getParameter('search'); $departmt = strip_tags(trim(strtolower($this->search))); if (preg_match('/(polyn[eé]sie)/i', $departmt)) { return $this->redirect('@list_parlementaires_departement?departement=Polyn%C3%A9sie_Fran%C3%A7aise'); } else { $departmt = preg_replace('/\s+/', '-', $departmt); if ($this->circo = Parlementaire::getNomDepartement(Parlementaire::getNumeroDepartement($departmt))) return $this->redirect('@list_parlementaires_departement?departement='.$this->circo); if (preg_match('/^(\d+\w?)$/', $departmt, $match)) { $num = preg_replace('/^0+/', '', $match[1]); $this->circo = Parlementaire::getNomDepartement($num); if ($this->circo) return $this->redirect('@list_parlementaires_departement?departement='.$this->circo); } $this->circo = $departmt; $ctquery = Doctrine_Query::create() ->from('Parlementaire p') ->select('count(*) as ct, p.nom_circo') ->where('nom_circo LIKE ?', '%'.$this->circo.'%') ->groupBy('nom_circo') ->fetchOne(); if ($ctquery['ct'] == 1) return $this->redirect('@list_parlementaires_departement?departement='.$ctquery['nom_circo']); $this->query_parlementaires = Doctrine::getTable('Parlementaire') ->createQuery('p') ->where('nom_circo LIKE ?', '%'.$this->circo.'%') ->addOrderBy('nom_circo, num_circo'); } } public function executeRedirect(sfWebRequest $request) { $departement = $request->getParameter('departement'); $num = $request->getParameter('numero'); $code = $request->getParameter('code'); if (preg_match('/0*([^0]\d*[ab]?)\-0*([^0]\d*)/', $code, $match)) { $departement = $match[1]; $num = $match[2]; } $parlementaire = Doctrine::getTable('Parlementaire')->createQuery('p') ->where('num_circo = ?', $num) ->andWhere('nom_circo = ?', parlementaire::getNomDepartement($departement)) ->andWhere('fin_mandat IS NULL') ->fetchOne(); if (!$parlementaire) { return $this->redirect('circonscription/list?departement='.$departement); } return $this->redirect('parlementaire/show?slug='.$parlementaire->slug); } } function url_for ($u) { return preg_replace ( '/@/', INITIATIVES_BASE_URL . "/", $u ); } ?>