+        * @param ?string $source The image source
+        * @param ?int $height The height
+        * @param ?int $width The width
+        * @return array The image array
+        */
+       public function getFacebook(string $path, array $texts, int $updated, ?string $source = null, ?int $height = null, ?int $width = null): array {
+               //Without source
+               if ($source === null && $this->config['facebook']['source'] === null) {
+                       //Return empty image data
+                       return [];
+               //Without local source
+               } elseif ($source === null) {
+                       //Set local source
+                       $source = $this->config['facebook']['source'];
+               }
+
+               //Without height
+               if ($height === null) {
+                       //Set height from config
+                       $height = $this->config['facebook']['height'];
+               }
+
+               //Without width
+               if ($width === null) {
+                       //Set width from config
+                       $width = $this->config['facebook']['width'];
+               }
+
+               //Set path file
+               $facebook = $this->config['cache'].'/'.$this->config['prefixes']['facebook'].$path.'.jpeg';
+
+               //Without existing path
+               if (!is_dir($dir = dirname($facebook))) {
+                       //Create filesystem object
+                       $filesystem = new Filesystem();
+
+                       try {
+                               //Create path
+                               //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
+                               $filesystem->mkdir($dir, 0775);
+                       } catch (IOExceptionInterface $e) {
+                               //Throw error
+                               throw new \Exception(sprintf('Output path "%s" do not exists and unable to create it', $dir), 0, $e);
+                       }
+               }
+
+               //With path file
+               if (is_file($facebook) && ($mtime = stat($facebook)['mtime']) && $mtime >= $updated) {
+                       #XXX: we used to drop texts with $data['canonical'] === true !!!
+
+                       //Set short path
+                       $short = $this->slugger->short($path);
+
+                       //Set hash
+                       $hash = $this->slugger->serialize([$short, $height, $width]);
+
+                       //Return image data
+                       return [
+                               'og:image' => $this->router->generate('rapsyspack_facebook', ['hash' => $hash, 'path' => $short, 'height' => $height, 'width' => $width, 'u' => $mtime, '_format' => $this->config['facebook']['format']], UrlGeneratorInterface::ABSOLUTE_URL),
+                               'og:image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))),
+                               'og:image:height' => $height,
+                               'og:image:width' => $width
+                       ];
+               }
+
+               //Set cache path
+               $cache = $this->config['cache'].'/'.$this->config['prefixes']['facebook'].$path.'.png';
+
+               //Without cache path
+               if (!is_dir($dir = dirname($cache))) {
+                       //Create filesystem object
+                       $filesystem = new Filesystem();
+
+                       try {
+                               //Create path
+                               //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
+                               $filesystem->mkdir($dir, 0775);
+                       } catch (IOExceptionInterface $e) {
+                               //Throw error
+                               throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
+                       }
+               }
+
+               //Create image object
+               $image = new \Imagick();
+
+               //Without cache image
+               if (!is_file($cache) || stat($cache)['mtime'] < stat($source)['mtime']) {
+                       //Check target directory
+                       if (!is_dir($dir = dirname($cache))) {
+                               //Create filesystem object
+                               $filesystem = new Filesystem();
+
+                               try {
+                                       //Create dir
+                                       //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
+                                       $filesystem->mkdir($dir, 0775);
+                               } catch (IOExceptionInterface $e) {
+                                       //Throw error
+                                       throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
+                               }
+                       }
+
+                       //Without source
+                       if (!is_file($source)) {
+                               //Throw error
+                               throw new \Exception(sprintf('Source file "%s" do not exists', $source));
+                       }
+
+                       //Convert to absolute path
+                       $source = realpath($source);
+
+                       //Read image
+                       //XXX: Imagick::readImage only supports absolute path
+                       $image->readImage($source);
+
+                       //Crop using aspect ratio
+                       //XXX: for better result upload image directly in aspect ratio :)
+                       $image->cropThumbnailImage($width, $height);
+
+                       //Strip image exif data and properties
+                       $image->stripImage();
+
+                       //Save cache image
+                       if (!$image->writeImage($cache)) {
+                               //Throw error
+                               throw new \Exception(sprintf('Unable to write image "%s"', $cache));
+                       }
+               //With cache
+               } else {
+                       //Read image
+                       $image->readImage($cache);
+               }
+
+               //Create draw
+               $draw = new \ImagickDraw();
+
+               //Set stroke antialias
+               $draw->setStrokeAntialias(true);
+
+               //Set text antialias
+               $draw->setTextAntialias(true);
+
+               //Set align aliases
+               $aligns = [
+                       'left' => \Imagick::ALIGN_LEFT,
+                       'center' => \Imagick::ALIGN_CENTER,
+                       'right' => \Imagick::ALIGN_RIGHT
+               ];
+
+               //Init counter
+               $i = 1;
+
+               //Set text count
+               $count = count($texts);
+
+               //Draw each text stroke
+               foreach($texts as $text => $data) {
+                       //Set font
+                       $draw->setFont($this->config['fonts'][$data['font']??$this->config['facebook']['font']]);
+
+                       //Set font size
+                       $draw->setFontSize($data['size']??$this->config['facebook']['size']);
+
+                       //Set stroke width
+                       $draw->setStrokeWidth($data['thickness']??$this->config['facebook']['thickness']);
+
+                       //Set text alignment
+                       $draw->setTextAlignment($align = ($aligns[$data['align']??$this->config['facebook']['align']]));
+
+                       //Get font metrics
+                       $metrics = $image->queryFontMetrics($draw, $text);
+
+                       //Without y
+                       if (empty($data['y'])) {
+                               //Position verticaly each text evenly
+                               $texts[$text]['y'] = $data['y'] = (($height + 100) / (count($texts) + 1) * $i) - 50;
+                       }
+
+                       //Without x
+                       if (empty($data['x'])) {
+                               if ($align == \Imagick::ALIGN_CENTER) {
+                                       $texts[$text]['x'] = $data['x'] = $width/2;
+                               } elseif ($align == \Imagick::ALIGN_LEFT) {
+                                       $texts[$text]['x'] = $data['x'] = 50;
+                               } elseif ($align == \Imagick::ALIGN_RIGHT) {
+                                       $texts[$text]['x'] = $data['x'] = $width - 50;
+                               }
+                       }
+
+                       //Center verticaly
+                       //XXX: add ascender part then center it back by half of textHeight
+                       //TODO: maybe add a boundingbox ???
+                       $texts[$text]['y'] = $data['y'] += $metrics['ascender'] - $metrics['textHeight']/2;
+
+                       //Set stroke color
+                       $draw->setStrokeColor(new \ImagickPixel($data['border']??$this->config['facebook']['border']));
+
+                       //Set fill color
+                       $draw->setFillColor(new \ImagickPixel($data['fill']??$this->config['facebook']['fill']));
+
+                       //Add annotation
+                       $draw->annotation($data['x'], $data['y'], $text);
+
+                       //Increase counter
+                       $i++;
+               }
+
+               //Create stroke object
+               $stroke = new \Imagick();
+
+               //Add new image
+               $stroke->newImage($width, $height, new \ImagickPixel('transparent'));
+
+               //Draw on image
+               $stroke->drawImage($draw);
+
+               //Blur image
+               //XXX: blur the stroke canvas only
+               $stroke->blurImage(5,3);
+
+               //Set opacity to 0.5
+               //XXX: see https://www.php.net/manual/en/image.evaluateimage.php
+               $stroke->evaluateImage(\Imagick::EVALUATE_DIVIDE, 1.5, \Imagick::CHANNEL_ALPHA);
+
+               //Compose image
+               $image->compositeImage($stroke, \Imagick::COMPOSITE_OVER, 0, 0);
+
+               //Clear stroke
+               $stroke->clear();
+
+               //Destroy stroke
+               unset($stroke);
+
+               //Clear draw
+               $draw->clear();
+
+               //Set text antialias
+               $draw->setTextAntialias(true);
+
+               //Draw each text
+               foreach($texts as $text => $data) {
+                       //Set font
+                       $draw->setFont($this->config['fonts'][$data['font']??$this->config['facebook']['font']]);
+
+                       //Set font size
+                       $draw->setFontSize($data['size']??$this->config['facebook']['size']);
+
+                       //Set text alignment
+                       $draw->setTextAlignment($aligns[$data['align']??$this->config['facebook']['align']]);
+
+                       //Set fill color
+                       $draw->setFillColor(new \ImagickPixel($data['fill']??$this->config['facebook']['fill']));
+
+                       //Add annotation
+                       $draw->annotation($data['x'], $data['y'], $text);
+
+                       //With canonical text
+                       if (!empty($data['canonical'])) {
+                               //Prevent canonical to finish in alt
+                               unset($texts[$text]);
+                       }
+               }
+
+               //Draw on image
+               $image->drawImage($draw);
+
+               //Strip image exif data and properties
+               $image->stripImage();
+
+               //Set image format
+               $image->setImageFormat('jpeg');
+
+               //Set progressive jpeg
+               $image->setInterlaceScheme(\Imagick::INTERLACE_PLANE);
+
+               //Save image
+               if (!$image->writeImage($facebook)) {
+                       //Throw error
+                       throw new \Exception(sprintf('Unable to write image "%s"', $facebook));
+               }
+
+               //Set short path
+               $short = $this->slugger->short($path);
+
+               //Set hash
+               $hash = $this->slugger->serialize([$short, $height, $width]);
+
+               //Return image data
+               return [
+                       'og:image' => $this->router->generate('rapsyspack_facebook', ['hash' => $hash, 'path' => $short, 'height' => $height, 'width' => $width, 'u' => stat($facebook)['mtime'], '_format' => $this->config['facebook']['format']], UrlGeneratorInterface::ABSOLUTE_URL),
+                       'og:image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))),
+                       'og:image:height' => $height,
+                       'og:image:width' => $width
+               ];
+       }
+
+       /**
+        * Get map data
+        *
+        * @param float $latitude The latitude
+        * @param float $longitude The longitude
+        * @param ?int $height The height
+        * @param ?int $width The width
+        * @param ?int $zoom The zoom
+        * @return array The map data
+        */
+       public function getMap(float $latitude, float $longitude, ?int $height = null, ?int $width = null, ?int $zoom = null): array {
+               //Without height
+               if ($height === null) {
+                       //Set height from config
+                       $height = $this->config['map']['height'];
+               }
+
+               //Without width
+               if ($width === null) {
+                       //Set width from config
+                       $width = $this->config['map']['width'];
+               }
+
+               //Without zoom
+               if ($zoom === null) {
+                       //Set zoom from config
+                       $zoom = $this->config['map']['zoom'];
+               }
+
+               //Set hash
+               $hash = $this->slugger->hash([$height, $width, $zoom, $latitude, $longitude]);
+
+               //Return array
+               return [
+                       'latitude' => $latitude,
+                       'longitude' => $longitude,
+                       'height' => $height,
+                       'src' => $this->router->generate('rapsyspack_map', ['hash' => $hash, 'height' => $height, 'width' => $width, 'zoom' => $zoom, 'latitude' => $latitude, 'longitude' => $longitude, '_format' => $this->config['map']['format']]),
+                       'width' => $width,
+                       'zoom' => $zoom
+               ];
+       }
+
+       /**
+        * Get multi map data
+        *
+        * @param array $coordinates The coordinates array
+        * @param ?int $height The height
+        * @param ?int $width The width
+        * @param ?int $zoom The zoom
+        * @return array The multi map data
+        */
+       public function getMulti(array $coordinates, ?int $height = null, ?int $width = null, ?int $zoom = null): array {
+               //Without coordinates
+               if ($coordinates === []) {
+                       //Throw error
+                       throw new \Exception('Missing coordinates');
+               }
+
+               //Without height
+               if ($height === null) {
+                       //Set height from config
+                       $height = $this->config['multi']['height'];
+               }
+
+               //Without width
+               if ($width === null) {
+                       //Set width from config
+                       $width = $this->config['multi']['width'];
+               }
+
+               //Without zoom
+               if ($zoom === null) {
+                       //Set zoom from config
+                       $zoom = $this->config['multi']['zoom'];
+               }
+
+               //Initialize latitudes and longitudes arrays
+               $latitudes = $longitudes = [];
+
+               //Set coordinate
+               $coordinate = implode(
+                       '-',
+                       array_map(
+                               function ($v) use (&$latitudes, &$longitudes) {
+                                       //Get latitude and longitude
+                                       list($latitude, $longitude) = $v;
+
+                                       //Append latitude
+                                       $latitudes[] = $latitude;
+
+                                       //Append longitude
+                                       $longitudes[] = $longitude;
+
+                                       //Append coordinate
+                                       return $latitude.','.$longitude;
+                               },
+                               $coordinates
+                       )
+               );
+
+               //Set latitude
+               $latitude = round((min($latitudes)+max($latitudes))/2, 6);
+
+               //Set longitude
+               $longitude = round((min($longitudes)+max($longitudes))/2, 6);
+
+               //Set zoom
+               $zoom = $this->getZoom($latitude, $longitude, $coordinates, $height, $width, $zoom);
+
+               //Set hash
+               $hash = $this->slugger->hash([$height, $width, $zoom, $coordinate]);
+
+               //Return array
+               return [
+                       'coordinate' => $coordinate,
+                       'height' => $height,
+                       'src' => $this->router->generate('rapsyspack_multi', ['hash' => $hash, 'height' => $height, 'width' => $width, 'zoom' => $zoom, 'coordinate' => $coordinate, '_format' => $this->config['multi']['format']]),
+                       'width' => $width,
+                       'zoom' => $zoom
+               ];
+       }
+
+       /**
+        * Get multi zoom
+        *
+        * Compute a zoom to have all coordinates on multi map
+        * Multi map visible only from -($width / 2) until ($width / 2) and from -($height / 2) until ($height / 2)
+        *
+        * @TODO Wether we need to take in consideration circle radius in coordinates comparisons, likely +/-(radius / $this->config['multi']['tz'])
+        *
+        * @param float $latitude The latitude
+        * @param float $longitude The longitude
+        * @param array $coordinates The coordinates array