/**
 * champlain_map_source_get_renderer:
 * @map_source: a #ChamplainMapSource
 *
 * Get the renderer used for tiles rendering.
 *
 * Returns: (transfer none): the renderer.
 *
 * Since: 0.8
 */
ChamplainRenderer *
champlain_map_source_get_renderer (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);

  return map_source->priv->renderer;
}
/**
 * champlain_map_source_get_tile_size:
 * @map_source: a #ChamplainMapSource
 *
 * Gets map source's tile size.
 *
 * Returns: the tile's size (width and height) in pixels for this map source
 *
 * Since: 0.4
 */
guint
champlain_map_source_get_tile_size (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);

  return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_tile_size (map_source);
}
/**
 * champlain_map_source_get_next_source:
 * @map_source: a #ChamplainMapSource
 *
 * Get the next source in the chain.
 *
 * Returns: (transfer none): the next source in the chain.
 *
 * Since: 0.6
 */
ChamplainMapSource *
champlain_map_source_get_next_source (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);

  return map_source->priv->next_source;
}
/**
 * champlain_map_source_get_projection:
 * @map_source: a #ChamplainMapSource
 *
 * Gets map source's projection.
 *
 * Returns: the map source's projection.
 *
 * Since: 0.4
 */
ChamplainMapProjection
champlain_map_source_get_projection (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), CHAMPLAIN_MAP_PROJECTION_MERCATOR);

  return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_projection (map_source);
}
/**
 * champlain_map_source_get_license_uri:
 * @map_source: a #ChamplainMapSource
 *
 * Gets map source's license URI.
 *
 * Returns: the map source's license URI.
 *
 * Since: 0.4
 */
const gchar *
champlain_map_source_get_license_uri (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), NULL);

  return CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->get_license_uri (map_source);
}
/**
 * champlain_map_source_fill_tile:
 * @map_source: a #ChamplainMapSource
 * @tile: a #ChamplainTile
 *
 * Fills the tile with image data (either from cache, network or rendered
 * locally).
 *
 * Since: 0.4
 */
void
champlain_map_source_fill_tile (ChamplainMapSource *map_source,
    ChamplainTile *tile)
{
  g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));

  CHAMPLAIN_MAP_SOURCE_GET_CLASS (map_source)->fill_tile (map_source, tile);
}
/**
 * champlain_map_source_get_column_count:
 * @map_source: a #ChamplainMapSource
 * @zoom_level: the zoom level
 *
 * Gets the number of tiles in a column at this zoom level for this map
 * source.
 *
 * Returns: the number of tiles in a column
 *
 * Since: 0.4
 */
guint
champlain_map_source_get_column_count (ChamplainMapSource *map_source,
    guint zoom_level)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);
  /* FIXME: support other projections */
  return (zoom_level != 0) ? 2 << (zoom_level - 1) : 1;
}
static const gchar *
get_license_uri (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_TILE_CACHE (map_source), NULL);

  ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);

  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (next_source), NULL);

  return champlain_map_source_get_license_uri (next_source);
}
static ChamplainMapProjection
get_projection (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_TILE_CACHE (map_source), CHAMPLAIN_MAP_PROJECTION_MERCATOR);

  ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);

  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (next_source), CHAMPLAIN_MAP_PROJECTION_MERCATOR);

  return champlain_map_source_get_projection (next_source);
}
static guint
get_tile_size (ChamplainMapSource *map_source)
{
  g_return_val_if_fail (CHAMPLAIN_IS_TILE_CACHE (map_source), 0);

  ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);

  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (next_source), 0);

  return champlain_map_source_get_tile_size (next_source);
}
static void
fill_tile (ChamplainMapSource *map_source,
    ChamplainTile *tile)
{
  g_return_if_fail (CHAMPLAIN_IS_MEMORY_CACHE (map_source));
  g_return_if_fail (CHAMPLAIN_IS_TILE (tile));

  ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);

  if (champlain_tile_get_state (tile) == CHAMPLAIN_STATE_DONE)
    return;

  if (champlain_tile_get_state (tile) != CHAMPLAIN_STATE_LOADED)
    {
      ChamplainMemoryCache *memory_cache = CHAMPLAIN_MEMORY_CACHE (map_source);
      ChamplainMemoryCachePrivate *priv = memory_cache->priv;
      ChamplainRenderer *renderer;
      GList *link;
      gchar *key;

      key = generate_queue_key (memory_cache, tile);
      link = g_hash_table_lookup (priv->hash_table, key);
      g_free (key);
      if (link)
        {
          QueueMember *member = link->data;

          move_queue_member_to_head (priv->queue, link);

          renderer = champlain_map_source_get_renderer (map_source);

          g_return_if_fail (CHAMPLAIN_IS_RENDERER (renderer));

          g_object_ref (map_source);
          g_object_ref (tile);

          g_signal_connect (tile, "render-complete", G_CALLBACK (tile_rendered_cb), map_source);

          champlain_renderer_set_data (renderer, member->data, member->size);
          champlain_renderer_render (renderer, tile);

          return;
        }
    }

  if (CHAMPLAIN_IS_MAP_SOURCE (next_source))
    champlain_map_source_fill_tile (next_source, tile);
  else if (champlain_tile_get_state (tile) == CHAMPLAIN_STATE_LOADED)
    {
      /* if we have some content, use the tile even if it wasn't validated */
      champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
      champlain_tile_display_content (tile);
    }
}
/**
 * champlain_map_source_get_x:
 * @map_source: a #ChamplainMapSource
 * @zoom_level: the zoom level
 * @longitude: a longitude
 *
 * Gets the x position on the map using this map source's projection.
 * (0, 0) is located at the top left.
 *
 * Returns: the x position
 *
 * Since: 0.4
 */
gdouble
champlain_map_source_get_x (ChamplainMapSource *map_source,
    guint zoom_level,
    gdouble longitude)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);

  longitude = CLAMP (longitude, CHAMPLAIN_MIN_LONGITUDE, CHAMPLAIN_MAX_LONGITUDE);

  /* FIXME: support other projections */
  return ((longitude + 180.0) / 360.0 * pow (2.0, zoom_level)) * champlain_map_source_get_tile_size (map_source);
}
/**
 * champlain_map_source_get_y:
 * @map_source: a #ChamplainMapSource
 * @zoom_level: the zoom level
 * @latitude: a latitude
 *
 * Gets the y position on the map using this map source's projection.
 * (0, 0) is located at the top left.
 *
 * Returns: the y position
 *
 * Since: 0.4
 */
gdouble
champlain_map_source_get_y (ChamplainMapSource *map_source,
    guint zoom_level,
    gdouble latitude)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0);

  latitude = CLAMP (latitude, CHAMPLAIN_MIN_LATITUDE, CHAMPLAIN_MAX_LATITUDE);

  /* FIXME: support other projections */
  return ((1.0 - log (tan (latitude * M_PI / 180.0) + 1.0 / cos (latitude * M_PI / 180.0)) / M_PI) /
          2.0 * pow (2.0, zoom_level)) * champlain_map_source_get_tile_size (map_source);
}
/**
 * champlain_map_source_set_next_source:
 * @map_source: a #ChamplainMapSource
 * @next_source: the next #ChamplainMapSource in the chain
 *
 * Sets the next map source in the chain.
 *
 * Since: 0.6
 */
void
champlain_map_source_set_next_source (ChamplainMapSource *map_source,
    ChamplainMapSource *next_source)
{
  g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));

  ChamplainMapSourcePrivate *priv = map_source->priv;

  if (priv->next_source != NULL)
    g_object_unref (priv->next_source);

  if (next_source)
    {
      g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (next_source));

      g_object_ref_sink (next_source);
    }

  priv->next_source = next_source;

  g_object_notify (G_OBJECT (map_source), "next-source");
}
/**
 * champlain_map_source_get_longitude:
 * @map_source: a #ChamplainMapSource
 * @zoom_level: the zoom level
 * @x: a x position
 *
 * Gets the longitude corresponding to this x position in the map source's
 * projection.
 *
 * Returns: the longitude
 *
 * Since: 0.4
 */
gdouble
champlain_map_source_get_longitude (ChamplainMapSource *map_source,
    guint zoom_level,
    gdouble x)
{
  gdouble longitude;

  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0.0);
  /* FIXME: support other projections */
  gdouble dx = (gdouble) x / champlain_map_source_get_tile_size (map_source);
  longitude = dx / pow (2.0, zoom_level) * 360.0 - 180.0;

  return CLAMP (longitude, CHAMPLAIN_MIN_LONGITUDE, CHAMPLAIN_MAX_LONGITUDE);
}
/**
 * champlain_map_source_get_latitude:
 * @map_source: a #ChamplainMapSource
 * @zoom_level: the zoom level
 * @y: a y position
 *
 * Gets the latitude corresponding to this y position in the map source's
 * projection.
 *
 * Returns: the latitude
 *
 * Since: 0.4
 */
gdouble
champlain_map_source_get_latitude (ChamplainMapSource *map_source,
    guint zoom_level,
    gdouble y)
{
  gdouble latitude;

  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0.0);
  /* FIXME: support other projections */
  gdouble dy = (gdouble) y / champlain_map_source_get_tile_size (map_source);
  gdouble n = M_PI - 2.0 * M_PI * dy / pow (2.0, zoom_level);
  latitude = 180.0 / M_PI *atan (0.5 * (exp (n) - exp (-n)));

  return CLAMP (latitude, CHAMPLAIN_MIN_LATITUDE, CHAMPLAIN_MAX_LATITUDE);
}
/**
 * champlain_map_source_set_renderer:
 * @map_source: a #ChamplainMapSource
 * @renderer: the renderer
 *
 * Sets the renderer used for tiles rendering.
 *
 * Since: 0.8
 */
void
champlain_map_source_set_renderer (ChamplainMapSource *map_source,
    ChamplainRenderer *renderer)
{
  g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));
  g_return_if_fail (CHAMPLAIN_IS_RENDERER (renderer));

  ChamplainMapSourcePrivate *priv = map_source->priv;

  if (priv->renderer != NULL)
    g_object_unref (priv->renderer);

  g_object_ref_sink (renderer);
  priv->renderer = renderer;

  g_object_notify (G_OBJECT (map_source), "renderer");
}
/**
 * champlain_map_source_get_meters_per_pixel:
 * @map_source: a #ChamplainMapSource
 * @zoom_level: the zoom level
 * @latitude: a latitude
 * @longitude: a longitude
 *
 * Gets meters per pixel at the position on the map using this map source's projection.
 *
 * Returns: the meters per pixel
 *
 * Since: 0.4.3
 */
gdouble
champlain_map_source_get_meters_per_pixel (ChamplainMapSource *map_source,
    guint zoom_level,
    gdouble latitude,
    G_GNUC_UNUSED gdouble longitude)
{
  g_return_val_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source), 0.0);

  /*
   * Width is in pixels. (1 px)
   * m/px = radius_at_latitude / width_in_pixels
   * k = radius of earth = 6 378.1 km
   * radius_at_latitude = 2pi * k * sin (pi/2-theta)
   */

  gdouble tile_size = champlain_map_source_get_tile_size (map_source);
  /* FIXME: support other projections */
  return 2.0 *M_PI *EARTH_RADIUS *sin (M_PI / 2.0 - M_PI / 180.0 *latitude) /
         (tile_size * champlain_map_source_get_row_count (map_source, zoom_level));
}
static void
fill_tile (ChamplainMapSource *map_source,
    ChamplainTile *tile)
{
  g_return_if_fail (CHAMPLAIN_IS_FILE_TILE_SOURCE (map_source));
  g_return_if_fail (CHAMPLAIN_IS_TILE (tile));

  ChamplainMapSource *next_source = champlain_map_source_get_next_source (map_source);

  if (champlain_tile_get_state (tile) == CHAMPLAIN_STATE_DONE)
    return;

  if (champlain_tile_get_state (tile) != CHAMPLAIN_STATE_LOADED)
    {
      ChamplainRenderer *renderer;

      renderer = champlain_map_source_get_renderer (map_source);

      g_return_if_fail (CHAMPLAIN_IS_RENDERER (renderer));

      g_object_ref (map_source);
      g_object_ref (tile);

      g_signal_connect (tile, "render-complete", G_CALLBACK (tile_rendered_cb), map_source);

      champlain_renderer_render (renderer, tile);
    }
  else if (CHAMPLAIN_IS_MAP_SOURCE (next_source))
    champlain_map_source_fill_tile (next_source, tile);
  else if (champlain_tile_get_state (tile) == CHAMPLAIN_STATE_LOADED)
    {
      /* if we have some content, use the tile even if it wasn't validated */
      champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
      champlain_tile_display_content (tile);
    }
}