source: cpc/trunk/project/apps/frontend/modules/circonscription/actions/actions.class.php @ 1408

Last change on this file since 1408 was 1408, checked in by roux, 11 years ago

map a partir du svg a partir de code de gabriel

File size: 13.9 KB
Line 
1<?php
2
3/**
4 * circonscription actions.
5 *
6 * @package    cpc
7 * @subpackage circonscription
8 * @author     Your name here
9 * @version    SVN: $Id: actions.class.php 12479 2008-10-31 10:54:40Z fabien $
10 */
11class circonscriptionActions extends sfActions
12{
13
14  /* Parse a transform attribute in a node and returns the associated
15   * translation.
16   * Only "translate" is supported.
17   * http://www.w3.org/TR/SVG/coords.html#TransformAttribute
18   */
19  private static function get_transform($n)
20  {
21    $r = array(0., 0.);
22
23    if($t = $n->getAttribute('transform')) {
24      if(preg_match_all(
25            "/(\w+)\s*\(\s*(-?\d+.?\d*)\s*(?:,\s*(-?\d+.?\d*))?\s*\)/U",
26            $t, $matches, PREG_SET_ORDER)) {
27        foreach ($matches as $m) {
28          switch($m[1]) {
29            case "translate":
30              $r[0] += $m[2];
31            $r[1] += $m[3];
32            break;
33            case "matrix":
34              case "scale":
35              case "rotate":
36              case "skewX":
37              case "skewY":
38            default:
39              trigger_error("Unsupported transform attribute: ".(string)$m[0],
40                  E_USER_ERROR);
41          }
42        }
43      } else {
44        trigger_error("Unsupported transform attribute: ".(string)$t,
45            E_USER_ERROR);
46      }
47    }
48    return $r;
49  }
50
51  /* Compute the coordinate system of a node.
52   * Only translations are supported.  viewBox attributes are ignored.
53   * http://www.w3.org/TR/SVG/coords.html#EstablishingANewUserSpace
54   */
55  private static function compose_transform($n)
56  {
57    $r = array(0., 0.);
58    do
59    {
60      $t = self::get_transform($n);
61      $r[0] += $t[0];
62      $r[1] += $t[1];
63      $n = $n->parentNode;
64    }
65    while($n->parentNode);
66
67    return $r;
68  }
69
70  /* Convert an svg path 'd' attribute into an HTML coords attribute
71   * $cs is the current coordinate system
72   * http://www.w3.org/TR/SVG/paths.html#PathData
73   * http://www.w3.org/TR/html40/struct/objects.html#adef-shape
74   */
75  private static function convert_path($data, $cs, $ratio_w, $ratio_h)
76  {
77    /* This is an ad-hoc hack which works quite well if the path has
78     * following form:
79     * M x,y L x,y L x,y ... L x,y z
80     * It does not handle the SVG spec, because PHP lacks proper parsing
81     * tools. It does NOT handle relative coordinates either.
82     * Bezier curves are converted to polygons following the control
83     * points --- yes, this is VERY ugly.
84     */
85    if (preg_match("/[^\sMCLz\d.,-]/",$data)) {
86      trigger_error("Unsupported path data attribute: ". $data,
87          E_USER_ERROR);
88      return NULL;
89    }
90    $points =  preg_split("/[\sCMLz]+/",$data, -1, PREG_SPLIT_NO_EMPTY);
91    foreach($points as $k => $p) {
92      $xy = preg_split("/,/", $p);
93      if (count($xy) != 2) {
94        trigger_error("Unsupported path data attribute: ". $data,
95            E_USER_ERROR);
96        return NULL;
97      }
98      $points[$k] = implode(",", array(($xy[0] + $cs[0]) * $ratio_w,
99            ($xy[1] + $cs[1]) * $ratio_h));
100    }
101    return (implode(",",$points));
102  }
103
104  /* Get the min and max x and y coordinates of an svg path 'd' attribute
105   * $cs is the current coordinate system
106   * http://www.w3.org/TR/SVG/paths.html#PathData
107   * http://www.w3.org/TR/html40/struct/objects.html#adef-shape
108   */
109  private static function path_minmax($data, $cs)
110  {
111    /* Same limitations as convert_path */
112    if (preg_match("/[^\sMCLz\d.,-]/",$data)) {
113      trigger_error("Unsupported path data attribute: ". $data,
114          E_USER_ERROR);
115      return NULL;
116    }
117    $points =  preg_split("/[\sCMLz]+/",$data, -1, PREG_SPLIT_NO_EMPTY);
118    foreach($points as $k => $p) {
119      $xy = preg_split("/,/", $p);
120      if (count($xy) != 2) {
121        trigger_error("Unsupported path data attribute: ". $data,
122            E_USER_ERROR);
123        return NULL;
124      }
125      $x[$k] = $xy[0] + $cs[0];
126      $y[$k] = $xy[1] + $cs[1];
127    }
128    return (array(
129          "minx" => min($x),
130          "miny" => min($y),
131          "maxx" => max($x),
132          "maxy" => max($y)));
133  }
134
135  /* Get the title of a given path node. */
136  private static function get_title($n)
137  {
138    $title = (string)
139      $n->getElementsByTagName('title')->item(0)->textContent;
140    $title .= " &mdash; ". (string)
141      $n->getElementsByTagName('desc')->item(0)->textContent;
142    return $title;
143  }
144
145  /* Compute the areas of an image map.
146   * $dom is the svg DOM, $w and $h the width and height of the resulting
147   * image - 0 if you want to extract this from the svg - and $regexp is a
148   * selects the nodes to include in the image map (based on their id).
149   * If only one of $w and $h is given, preserve the svg ratio.
150   */
151  private static function compute_areas($dom, $w, $h, $regexp, $deptitle = 0)
152  {
153    $areas = "";
154
155    $svg = $dom->getElementsByTagName('svg')->item(0);
156
157    $svg_w = (string) $svg->getAttribute('width');
158    $svg_h = (string) $svg->getAttribute('height');
159
160    if ($w == 0 && $h == 0)
161    {
162      $w = (int) $svg_w;
163      $h = (int) $svg_h;
164    }
165    elseif ($w == 0)
166      $w = (int) ($svg_w * $h / $svg_h);
167    elseif ($h == 0)
168      $h = (int) ($svg_h * $w / $svg_w);
169
170    $ratio_w = $w / $svg_w;
171    $ratio_h = $h / $svg_h;
172
173    $paths = $dom->getElementsByTagName('path');
174
175    foreach ($paths as $path)
176      if (preg_match($regexp, $path->getAttribute('id'))) {
177        $cs = self::compose_transform($path);
178        $points = self::convert_path($path->getAttribute('d'), $cs, $ratio_w, $ratio_h);
179        if ($deptitle) {
180          $title = $path->getAttribute('title');
181          $id = preg_replace('/d/', '', $path->getAttribute('id'));
182          $href = url_for("@list_parlementaires_circo_search?search=$id");
183        } else {
184          $id = $path->getAttribute('id');
185          $title = self::get_title($path);
186          $href = url_for("@redirect_parlementaires_circo?code=".$path->getAttribute('id'));
187        }
188        $areas .= "<area id=\"map$id\" href=\"".$href."\" title=\"".$title."\" ".
189          "shape=\"poly\" coords=\"".$points."\"></area>\n";
190      }
191    return array('areas' => $areas, 'w' => $w, 'h' => $h);
192  }
193
194  /* Crop an svg dom to keep only the $tags which fullfill the
195   * $regexp condition. Any other path is removed, and the
196   * image is cropped so as to focus on the remaining paths, with some
197   * $margin.
198   */
199  private static function crop_svg($dom, $regexp, $margin, $tags = array('path', 'text'))
200  {
201    $svg = $dom->getElementsByTagName('svg')->item(0);
202
203    $toRemove = array();
204
205    foreach($tags as $tag) {
206      $paths = $dom->getElementsByTagName($tag);
207      foreach ($paths as $path) {
208        if (preg_match($regexp, $path->getAttribute('id'))) {
209          $cs = self::compose_transform($path);
210          $t = self::path_minmax($path->getAttribute('d'), $cs);
211          $minx[] = $t["minx"];
212          $maxx[] = $t["maxx"];
213          $miny[] = $t["miny"];
214          $maxy[] = $t["maxy"];
215        }
216        else {
217          /* WARNING You can't remove DOMNodes from a DOMNodeList as you're
218           * iterating over them in a foreach loop. */
219          $toRemove[] = $path;
220        }
221      }
222    }
223
224    foreach($toRemove as $node) {
225      $node->parentNode->removeChild($node);
226    }
227
228    $x_min = min($minx) - $margin;
229    $x_max = max($maxx) + $margin;
230    $y_min = min($miny) - $margin;
231    $y_max = max($maxy) + $margin;
232
233    $svg->setAttribute('width', $x_max - $x_min);
234    $svg->setAttribute('height', $y_max - $y_min);
235    $svg->setAttribute('transform', "translate(".-$x_min.",".-$y_min.")");
236  }
237
238  private static function generateSvgDep($w, $h) {
239    $dom = new DOMDocument();
240    $dom->preserveWhiteSpace = FALSE;
241    // FIXME Use loadXML to load from a string instead (database)
242    $dom->load("france_deptmts.svg");
243    return $dom;
244  }
245
246  public static function echoDeptmtsMap($w, $h) {
247    $dom = self::generateSvgDep($w, $h);
248    $r = self::compute_areas($dom, $w, $h, '/^d\d+/', 1);
249    $w = $r['w'];
250    $h = $r['h'];
251
252    $src = url_for("@deptmts_image_png?w=$w&h=$h");
253
254    echo "<img class=\"carte_departement\" src=\"$src\" usemap=\"#deptmts\" ";
255    echo 'style="width:'.$w.'px; height:'.$h.'px;" />';
256    echo "<map name=\"deptmts\">";
257    echo $r['areas'];
258    echo "</map>";
259  }
260
261  private static function echoDeptmtsImage($w, $h) {
262    $dom = self::generateSvgDep($w, $h);
263
264    $im = new Imagick();
265    $im->readImageBlob($dom->saveXML());
266    $res = $im->getImageResolution();
267    $x_ratio = $res['x'] / $im->getImageWidth();
268    $y_ratio = $res['y'] / $im->getImageHeight();
269    $im->removeImage();
270    $im->setResolution($w * $x_ratio, $h * $y_ratio);
271    $im->readImageBlob($dom->saveXML());
272
273    $im->setImageFormat("png");
274    echo $im;
275  }
276
277  private static function generateSvgDom($circo, $w, $h)
278  {
279    $dom = new DOMDocument();
280    $dom->preserveWhiteSpace = FALSE;
281    // FIXME Use loadXML to load from a string instead (database)
282    $dom->load("circo.svg");
283
284    if(preg_match("/^\d\d[\dab]$/",$circo))
285      self::crop_svg($dom, "/^$circo-\d\d$/", 10);
286
287    return $dom;
288
289  }
290
291  /* $circo is a three digits string, or "full" for the full map */
292  public static function echoCircoMap($circo, $w, $h)
293  {
294    $dom = self::generateSvgDom($circo, $w, $h);
295
296    if($circo == "full")
297      $regexp = "/^\d\d[\dab]-(0[1-9]|[1-9]\d)$/";
298    else
299      $regexp = "/^$circo-(0[1-9]|[1-9]\d)$/";
300
301    $r = self::compute_areas($dom, $w, $h, $regexp);
302    $w = $r['w'];
303    $h = $r['h'];
304
305    $src = url_for("@circo_image_png?circo=$circo&w=$w&h=$h");
306
307    echo "<img class=\"carte_departement\" src=\"$src\" usemap=\"#$circo\" ";
308    echo 'style="width:'.$w.'px; height:'.$h.'px;" />';
309    echo "<map name=\"$circo\">";
310    echo $r['areas'];
311    echo "</map>";
312  }
313
314  /* $circo is a three digits string, or "full" for the full map */
315  private static function echoCircoImage($circo, $w, $h)
316  {
317
318    /* If you want to resize a vector-graphics image (such as SVG) to a
319     * certain dimension in pixels, without losing quality, you have to do
320     * this
321     * http://www.php.net/manual/en/function.imagick-setresolution.php
322     */
323
324    $dom = self::generateSvgDom($circo, $w, $h);
325
326    $im = new Imagick();
327    $im->readImageBlob($dom->saveXML());
328    $res = $im->getImageResolution();
329    $x_ratio = $res['x'] / $im->getImageWidth();
330    $y_ratio = $res['y'] / $im->getImageHeight();
331    $im->removeImage();
332    $im->setResolution($w * $x_ratio, $h * $y_ratio);
333    $im->readImageBlob($dom->saveXML());
334
335    $im->setImageFormat("png");
336    echo $im;
337  }
338
339  public function executeGetDeptmtsimagepng(sfWebRequest $request) {
340    $w = $request->getParameter('w');
341    $h = $request->getParameter('h');
342    header("Content-type: image/png");
343    self::echoDeptmtsImage($w, $h);
344    return sfView::NONE;
345  }
346
347  public function executeGetCircoimagepng(sfWebRequest $request)
348  {
349    $circo = $request->getParameter('circo');
350    $w = $request->getParameter('w');
351    $h = $request->getParameter('h');
352    header("Content-type: image/png");
353    self::echoCircoImage($circo, $w, $h);
354    return sfView::NONE;
355  }
356
357  public function executeList(sfWebRequest $request)
358  {
359    $this->circos = Parlementaire::$dptmt_nom;
360  }
361  public function executeMap(sfWebRequest $request)
362  {
363  }
364  public function executeShow(sfWebRequest $request)
365  {
366    $this->circo = preg_replace('/_/', ' ', $request->getParameter('departement'));
367    $this->forward404Unless($this->circo);
368    $this->departement_num = Parlementaire::getNumeroDepartement($this->circo);
369
370    $this->parlementaires = Doctrine::getTable('Parlementaire')->createQuery('p')
371      ->where('p.nom_circo = ?', $this->circo)
372      ->addOrderBy('p.num_circo')
373      ->execute();
374    $this->total = count($this->parlementaires);
375    $this->forward404Unless($this->total);
376    if ($this->total == 1)
377        return $this->redirect('@parlementaire?slug='.$this->parlementaires[0]['slug']);
378  }
379  public function executeSearch(sfWebRequest $request)
380  {
381    $this->search = $request->getParameter('search');
382    $departmt = strip_tags(trim(strtolower($this->search)));
383    if (preg_match('/(polyn[eé]sie)/i', $departmt)) {
384      return $this->redirect('@list_parlementaires_departement?departement=Polyn%C3%A9sie_Fran%C3%A7aise');
385    } else {
386      $departmt = preg_replace('/\s+/', '-', $departmt);
387      if ($this->circo = Parlementaire::getNomDepartement(Parlementaire::getNumeroDepartement($departmt)))
388        return $this->redirect('@list_parlementaires_departement?departement='.$this->circo);
389      if (preg_match('/^(\d+\w?)$/', $departmt, $match)) {
390        $num = preg_replace('/^0+/', '', $match[1]);
391        $this->circo = Parlementaire::getNomDepartement($num);
392        if ($this->circo)
393          return $this->redirect('@list_parlementaires_departement?departement='.$this->circo);
394      }
395      $this->circo = $departmt;
396      $ctquery = Doctrine_Query::create()
397        ->from('Parlementaire p')
398        ->select('count(*) as ct, p.nom_circo')
399        ->where('nom_circo LIKE ?', '%'.$this->circo.'%')
400        ->groupBy('nom_circo')
401        ->fetchOne();
402      if ($ctquery['ct'] == 1)
403        return $this->redirect('@list_parlementaires_departement?departement='.$ctquery['nom_circo']);
404      $this->query_parlementaires = Doctrine::getTable('Parlementaire')
405        ->createQuery('p')
406        ->where('nom_circo LIKE ?', '%'.$this->circo.'%')
407        ->addOrderBy('nom_circo, num_circo');
408    }
409  }
410  public function executeRedirect(sfWebRequest $request)
411  {
412    $departement = $request->getParameter('departement');
413    $num = $request->getParameter('numero');
414    $code = $request->getParameter('code');
415    if (preg_match('/0*([^0]\d*[ab]?)\-0*([^0]\d*)/', $code, $match)) {
416      $departement = $match[1];
417      $num = $match[2];
418    }
419    $parlementaire = Doctrine::getTable('Parlementaire')->createQuery('p')
420      ->where('num_circo = ?', $num)
421      ->andWhere('nom_circo = ?', parlementaire::getNomDepartement($departement))
422      ->andWhere('fin_mandat IS NULL')
423      ->fetchOne();
424    if (!$parlementaire) {
425      return $this->redirect('circonscription/list?departement='.$departement);
426    }
427    return $this->redirect('parlementaire/show?slug='.$parlementaire->slug);
428  }
429}
Note: See TracBrowser for help on using the repository browser.