#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <caca.h>
#include <gtcaca/map.h>
#include <gtcaca/main.h>
static const gtcaca_map_city_t g_cities[] = {
{ "Washington", 38.90, -77.04 }, { "New York", 40.71, -74.01 },
{ "San Francisco", 37.77, -122.42 }, { "Los Angeles", 34.05, -118.24 },
{ "Chicago", 41.88, -87.63 }, { "Seattle", 47.61, -122.33 },
{ "Boston", 42.36, -71.06 }, { "Houston", 29.76, -95.37 },
{ "Miami", 25.76, -80.19 }, { "Toronto", 43.65, -79.38 },
{ "Ottawa", 45.42, -75.70 }, { "Montreal", 45.50, -73.57 },
{ "Vancouver", 49.28, -123.12 }, { "Mexico City", 19.43, -99.13 },
{ "Havana", 23.11, -82.37 }, { "Guatemala City", 14.63, -90.51 },
{ "Brasilia", -15.79, -47.88 }, { "Rio de Janeiro", -22.91, -43.17 },
{ "Sao Paulo", -23.55, -46.63 }, { "Buenos Aires", -34.60, -58.38 },
{ "Lima", -12.05, -77.04 }, { "Bogota", 4.71, -74.07 },
{ "Santiago", -33.45, -70.67 }, { "Caracas", 10.48, -66.90 },
{ "Quito", -0.18, -78.47 }, { "La Paz", -16.50, -68.15 },
{ "Montevideo", -34.90, -56.16 }, { "Asuncion", -25.30, -57.64 },
{ "London", 51.51, -0.13 }, { "Paris", 48.85, 2.35 },
{ "Berlin", 52.52, 13.40 }, { "Madrid", 40.42, -3.70 },
{ "Rome", 41.90, 12.50 }, { "Lisbon", 38.72, -9.14 },
{ "Amsterdam", 52.37, 4.90 }, { "Brussels", 50.85, 4.35 },
{ "Vienna", 48.21, 16.37 }, { "Bern", 46.95, 7.45 },
{ "Zurich", 47.37, 8.54 }, { "Barcelona", 41.39, 2.17 },
{ "Munich", 48.14, 11.58 }, { "Milan", 45.46, 9.19 },
{ "Dublin", 53.35, -6.26 }, { "Copenhagen", 55.68, 12.57 },
{ "Stockholm", 59.33, 18.07 }, { "Oslo", 59.91, 10.75 },
{ "Helsinki", 60.17, 24.94 }, { "Warsaw", 52.23, 21.01 },
{ "Prague", 50.09, 14.42 }, { "Budapest", 47.50, 19.04 },
{ "Athens", 37.98, 23.73 }, { "Moscow", 55.76, 37.62 },
{ "Saint Petersburg", 59.93, 30.34 }, { "Kyiv", 50.45, 30.52 },
{ "Bucharest", 44.43, 26.10 }, { "Belgrade", 44.79, 20.45 },
{ "Sofia", 42.70, 23.32 }, { "Reykjavik", 64.15, -21.94 },
{ "Istanbul", 41.01, 28.98 }, { "Ankara", 39.93, 32.86 },
{ "Jerusalem", 31.78, 35.22 }, { "Tel Aviv", 32.07, 34.78 },
{ "Riyadh", 24.71, 46.68 }, { "Dubai", 25.20, 55.27 },
{ "Abu Dhabi", 24.45, 54.38 }, { "Doha", 25.29, 51.53 },
{ "Kuwait City", 29.38, 47.99 }, { "Baghdad", 33.31, 44.36 },
{ "Tehran", 35.69, 51.39 }, { "Amman", 31.95, 35.93 },
{ "Beirut", 33.89, 35.50 }, { "Damascus", 33.51, 36.29 },
{ "Cairo", 30.04, 31.24 }, { "Lagos", 6.52, 3.38 },
{ "Nairobi", -1.29, 36.82 }, { "Cape Town", -33.92, 18.42 },
{ "Johannesburg", -26.20, 28.05 }, { "Pretoria", -25.75, 28.19 },
{ "Casablanca", 33.57, -7.59 }, { "Rabat", 34.02, -6.83 },
{ "Accra", 5.60, -0.19 }, { "Addis Ababa", 9.03, 38.74 },
{ "Algiers", 36.75, 3.06 }, { "Tunis", 36.81, 10.18 },
{ "Dakar", 14.69, -17.45 }, { "Khartoum", 15.50, 32.56 },
{ "Kinshasa", -4.32, 15.31 }, { "Luanda", -8.84, 13.23 },
{ "Harare", -17.83, 31.05 }, { "Dar es Salaam", -6.79, 39.21 },
{ "Tokyo", 35.68, 139.69 }, { "Osaka", 34.69, 135.50 },
{ "Beijing", 39.90, 116.41 }, { "Shanghai", 31.23, 121.47 },
{ "Hong Kong", 22.32, 114.17 }, { "Seoul", 37.57, 126.98 },
{ "Taipei", 25.03, 121.57 }, { "Delhi", 28.61, 77.21 },
{ "Mumbai", 19.08, 72.88 }, { "Bangalore", 12.97, 77.59 },
{ "Kolkata", 22.57, 88.36 }, { "Chennai", 13.08, 80.27 },
{ "Bangkok", 13.76, 100.50 }, { "Singapore", 1.35, 103.82 },
{ "Jakarta", -6.21, 106.85 }, { "Kuala Lumpur", 3.14, 101.69 },
{ "Manila", 14.60, 120.98 }, { "Hanoi", 21.03, 105.85 },
{ "Ho Chi Minh City", 10.82, 106.63 }, { "Kathmandu", 27.72, 85.32 },
{ "Dhaka", 23.81, 90.41 }, { "Islamabad", 33.69, 73.06 },
{ "Karachi", 24.86, 67.00 }, { "Colombo", 6.93, 79.86 },
{ "Ulaanbaatar", 47.89, 106.91 }, { "Almaty", 43.24, 76.95 },
{ "Tashkent", 41.30, 69.24 },
{ "Sydney", -33.87, 151.21 }, { "Melbourne", -37.81, 144.96 },
{ "Canberra", -35.28, 149.13 }, { "Brisbane", -27.47, 153.03 },
{ "Perth", -31.95, 115.86 }, { "Auckland", -36.85, 174.76 },
{ "Wellington", -41.29, 174.78 },
};
static int city_name_eq(const char *a, const char *b)
{
while (*a == ' ' || *a == '\t') a++;
while (*b == ' ' || *b == '\t') b++;
while (*a && *b) {
if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) return 0;
a++; b++;
}
while (*a == ' ' || *a == '\t') a++;
while (*b == ' ' || *b == '\t') b++;
return *a == '\0' && *b == '\0';
}
const gtcaca_map_city_t *gtcaca_map_cities(int *count)
{
if (count) *count = (int)(sizeof g_cities / sizeof g_cities[0]);
return g_cities;
}
const gtcaca_map_city_t *gtcaca_map_find_city(const char *name)
{
int i, n = (int)(sizeof g_cities / sizeof g_cities[0]);
if (!name) return NULL;
for (i = 0; i < n; i++)
if (city_name_eq(name, g_cities[i].name)) return &g_cities[i];
return NULL;
}
int gtcaca_map_add_city(gtcaca_map_widget_t *m, const char *name, uint32_t glyph, uint8_t colour)
{
const gtcaca_map_city_t *c = gtcaca_map_find_city(name);
if (!c) return 0;
gtcaca_map_add_point(m, c->lat, c->lon, glyph, colour, c->name);
return 1;
}
gtcaca_map_widget_t *gtcaca_map_new(gtcaca_widget_t *parent, int x, int y, int width, int height)
{
gtcaca_map_widget_t *m = malloc(sizeof(*m));
if (!m) { fprintf(stderr, "Error: Cannot allocate map\n"); return NULL; }
m->id = gtcaca_get_newid();
m->has_focus = 0;
m->is_visible = 1;
m->type = GTCACA_WIDGET_MAP;
m->parent = parent;
m->children = NULL;
gtcaca_widget_position_size_parent(parent, GTCACA_WIDGET(m), x, y);
m->width = width; m->height = height;
m->color_focus_fg = m->color_nonfocus_fg = gmo.theme.textview.fg;
m->color_focus_bg = m->color_nonfocus_bg = gmo.theme.textview.bg;
m->lon_min = -180.0; m->lon_max = 180.0;
m->lat_min = -90.0; m->lat_max = 90.0;
m->points = NULL; m->npoints = 0; m->cappoints = 0;
m->polys = NULL; m->npolys = 0; m->cappolys = 0;
m->graticule = 1;
m->sel = -1;
m->title = NULL;
CDL_APPEND(gmo.widgets_list, GTCACA_WIDGET(m));
return m;
}
void gtcaca_map_clear(gtcaca_map_widget_t *m)
{
int i;
for (i = 0; i < m->npoints; i++) free(m->points[i].label);
for (i = 0; i < m->npolys; i++) free(m->polys[i].lonlat);
m->npoints = 0; m->npolys = 0;
}
void gtcaca_map_free(gtcaca_map_widget_t *m)
{
if (!m) return;
gtcaca_map_clear(m);
free(m->points); free(m->polys); free(m->title); free(m);
}
void gtcaca_map_set_bounds(gtcaca_map_widget_t *m, double lon_min, double lon_max,
double lat_min, double lat_max)
{ m->lon_min = lon_min; m->lon_max = lon_max; m->lat_min = lat_min; m->lat_max = lat_max; }
void gtcaca_map_add_point(gtcaca_map_widget_t *m, double lat, double lon,
uint32_t glyph, uint8_t colour, const char *label)
{
gtcaca_map_point_t *p;
if (m->npoints == m->cappoints) {
int nc = m->cappoints ? m->cappoints * 2 : 32;
gtcaca_map_point_t *q = realloc(m->points, (size_t)nc * sizeof(*q));
if (!q) return;
m->points = q; m->cappoints = nc;
}
p = &m->points[m->npoints++];
p->lat = lat; p->lon = lon; p->glyph = glyph ? glyph : '*'; p->colour = colour;
p->label = label ? strdup(label) : NULL;
}
void gtcaca_map_add_polyline(gtcaca_map_widget_t *m, const double *lonlat, int n, uint8_t colour)
{
gtcaca_map_poly_t *p;
if (n < 2) return;
if (m->npolys == m->cappolys) {
int nc = m->cappolys ? m->cappolys * 2 : 16;
gtcaca_map_poly_t *q = realloc(m->polys, (size_t)nc * sizeof(*q));
if (!q) return;
m->polys = q; m->cappolys = nc;
}
p = &m->polys[m->npolys++];
p->lonlat = malloc((size_t)n * 2 * sizeof(double));
if (!p->lonlat) { m->npolys--; return; }
memcpy(p->lonlat, lonlat, (size_t)n * 2 * sizeof(double));
p->n = n; p->colour = colour;
}
void gtcaca_map_set_graticule(gtcaca_map_widget_t *m, int on) { m->graticule = on ? 1 : 0; }
void gtcaca_map_set_title(gtcaca_map_widget_t *m, const char *title)
{ free(m->title); m->title = title ? strdup(title) : NULL; }
int gtcaca_map_project(gtcaca_map_widget_t *m, double lon, double lat, int *sx, int *sy)
{
int inner_x = m->x + 1, inner_y = m->y + 1, inner_w = m->width - 2, inner_h = m->height - 2;
double fx, fy;
if (inner_w <= 0 || inner_h <= 0) return 0;
if (m->lon_max == m->lon_min || m->lat_max == m->lat_min) return 0;
fx = (lon - m->lon_min) / (m->lon_max - m->lon_min);
fy = (m->lat_max - lat) / (m->lat_max - m->lat_min);
if (fx < 0.0 || fx > 1.0 || fy < 0.0 || fy > 1.0) return 0;
*sx = inner_x + (int)(fx * (inner_w - 1) + 0.5);
*sy = inner_y + (int)(fy * (inner_h - 1) + 0.5);
return 1;
}
static const double _WORLD_NA[] = {
-168,65,-165,60,-153,58,-135,57,-130,52,-123,48,-124,40,-117,32,-110,23,-105,20,
-97,16,-90,15,-83,9,-81,18,-80,26,-75,35,-70,42,-67,45,-60,47,-56,52,-64,60,
-78,62,-95,69,-125,70,-156,71,-168,65 };
static const double _WORLD_SA[] = {
-78,8,-70,11,-60,10,-50,0,-44,-3,-35,-6,-39,-13,-48,-25,-54,-34,-62,-40,-66,-45,
-71,-52,-75,-50,-73,-42,-71,-30,-76,-16,-81,-6,-80,2,-78,8 };
static const double _WORLD_AF[] = {
-16,15,-16,21,-10,28,0,32,10,34,20,33,32,31,35,24,43,12,51,12,42,-2,40,-12,
35,-22,25,-34,18,-34,12,-18,9,-2,5,5,-8,5,-16,12,-16,15 };
static const double _WORLD_EU[] = {
-10,36,-9,43,-2,48,2,51,4,58,10,63,25,71,30,66,40,60,40,50,28,45,18,42,8,44,0,40,-10,36 };
static const double _WORLD_AS[] = {
26,40,30,47,50,50,60,45,70,55,90,52,110,52,130,53,142,50,140,42,130,35,122,30,
121,22,109,21,105,10,100,7,98,16,90,22,80,8,77,20,70,25,60,25,52,28,45,37,35,38,26,40 };
static const double _WORLD_AU[] = {
114,-22,122,-18,130,-12,137,-12,142,-11,146,-18,150,-25,150,-37,143,-39,135,-35,
129,-32,123,-34,115,-35,113,-26,114,-22 };
void gtcaca_map_add_world(gtcaca_map_widget_t *m, uint8_t colour)
{
gtcaca_map_add_polyline(m, _WORLD_NA, (int)(sizeof _WORLD_NA / sizeof(double) / 2), colour);
gtcaca_map_add_polyline(m, _WORLD_SA, (int)(sizeof _WORLD_SA / sizeof(double) / 2), colour);
gtcaca_map_add_polyline(m, _WORLD_AF, (int)(sizeof _WORLD_AF / sizeof(double) / 2), colour);
gtcaca_map_add_polyline(m, _WORLD_EU, (int)(sizeof _WORLD_EU / sizeof(double) / 2), colour);
gtcaca_map_add_polyline(m, _WORLD_AS, (int)(sizeof _WORLD_AS / sizeof(double) / 2), colour);
gtcaca_map_add_polyline(m, _WORLD_AU, (int)(sizeof _WORLD_AU / sizeof(double) / 2), colour);
}
void gtcaca_map_draw(gtcaca_map_widget_t *m)
{
uint8_t fg = gmo.theme.textview.fg, bg = gmo.theme.textview.bg;
int inner_x = m->x + 1, inner_y = m->y + 1, inner_w = m->width - 2, inner_h = m->height - 2;
int i, j, sx, sy;
caca_set_color_ansi(gmo.cv, fg, bg); caca_set_attr(gmo.cv, 0);
caca_fill_box(gmo.cv, m->x, m->y, m->width, m->height, ' ');
caca_draw_cp437_box(gmo.cv, m->x, m->y, m->width, m->height);
if (m->title) caca_printf(gmo.cv, m->x + 2, m->y, "| %s |", m->title);
if (inner_w <= 0 || inner_h <= 0) return;
if (m->graticule) {
double lon, lat;
caca_set_color_ansi(gmo.cv, CACA_DARKGRAY, bg); caca_set_attr(gmo.cv, 0);
for (lon = -150.0; lon <= 150.0 + 0.01; lon += 30.0)
for (lat = m->lat_min; lat <= m->lat_max; lat += (m->lat_max - m->lat_min) / (inner_h * 2))
if (gtcaca_map_project(m, lon, lat, &sx, &sy)) caca_put_char(gmo.cv, sx, sy, ':');
for (lat = -60.0; lat <= 60.0 + 0.01; lat += 30.0)
for (lon = m->lon_min; lon <= m->lon_max; lon += (m->lon_max - m->lon_min) / (inner_w * 2))
if (gtcaca_map_project(m, lon, lat, &sx, &sy)) caca_put_char(gmo.cv, sx, sy, '.');
}
for (i = 0; i < m->npolys; i++) {
gtcaca_map_poly_t *p = &m->polys[i];
int px = 0, py = 0, have = 0;
caca_set_color_ansi(gmo.cv, p->colour, bg); caca_set_attr(gmo.cv, 0);
for (j = 0; j < p->n; j++) {
if (gtcaca_map_project(m, p->lonlat[j * 2], p->lonlat[j * 2 + 1], &sx, &sy)) {
if (have) caca_draw_line(gmo.cv, px, py, sx, sy, 0x2588);
px = sx; py = sy; have = 1;
} else have = 0;
}
}
for (i = 0; i < m->npoints; i++)
if (gtcaca_map_project(m, m->points[i].lon, m->points[i].lat, &sx, &sy)) {
int selected = (i == m->sel);
if (selected) { caca_set_color_ansi(gmo.cv, CACA_BLACK, m->points[i].colour); }
else { caca_set_color_ansi(gmo.cv, m->points[i].colour, bg); }
caca_set_attr(gmo.cv, CACA_BOLD);
caca_put_char(gmo.cv, sx, sy, m->points[i].glyph);
if (m->points[i].label) {
caca_set_color_ansi(gmo.cv, selected ? CACA_WHITE : m->points[i].colour, bg);
caca_set_attr(gmo.cv, selected ? CACA_BOLD : 0);
caca_printf(gmo.cv, sx + 1, sy, "%-.*s",
inner_x + inner_w - (sx + 1) > 0 ? inner_x + inner_w - (sx + 1) : 0,
m->points[i].label);
}
}
caca_set_attr(gmo.cv, 0);
}
int gtcaca_map_selected(gtcaca_map_widget_t *m) { return m->sel; }
int gtcaca_map_key(gtcaca_map_widget_t *m, int key, void *userdata)
{
int dirx = 0, diry = 0, i, best = -1, csx, csy;
long bestd = 0;
(void)userdata;
if (m->npoints == 0) return 0;
if (m->sel < 0) m->sel = 0;
switch (key) {
case CACA_KEY_TAB: m->sel = (m->sel + 1) % m->npoints; return 1;
case CACA_KEY_UP: diry = -1; break;
case CACA_KEY_DOWN: diry = 1; break;
case CACA_KEY_LEFT: dirx = -1; break;
case CACA_KEY_RIGHT: dirx = 1; break;
default: return 0;
}
if (!gtcaca_map_project(m, m->points[m->sel].lon, m->points[m->sel].lat, &csx, &csy)) return 1;
for (i = 0; i < m->npoints; i++) {
int sx, sy; long d;
if (i == m->sel) continue;
if (!gtcaca_map_project(m, m->points[i].lon, m->points[i].lat, &sx, &sy)) continue;
if (dirx && (sx - csx) * dirx <= 0) continue;
if (diry && (sy - csy) * diry <= 0) continue;
d = (long)(sx - csx) * (sx - csx) + (long)(sy - csy) * (sy - csy);
if (best < 0 || d < bestd) { bestd = d; best = i; }
}
if (best >= 0) m->sel = best;
return 1;
}