// // Copyright 2006-2009 Johannes Hofmann // // This software may be used and distributed according to the terms // of the GNU General Public License, incorporated herein by reference. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Fl_Search_Chooser.H" #include "choose_hill.H" #include "ScanImage.H" #include "GipfelWidget.H" #define CROSS_SIZE 2 static double pi_d, deg2rad; GipfelWidget::GipfelWidget(int X,int Y,int W, int H, void (*changed_cb)()): Fl_Group(X, Y, W, H) { end(); pi_d = asin(1.0) * 2.0; deg2rad = pi_d / 180.0; img = NULL; pan = new Panorama(); cur_mountain = NULL; focused_mountain = NULL; known_hills = new Hills(); img_file = NULL; track_width = 200.0; show_hidden = false; have_gipfel_info = false; md = new ImageMetaData(); track_points = NULL; fl_register_images(); mouse_x = mouse_y = 0; params_changed_cb = changed_cb; } int GipfelWidget::load_image(char *file) { Fl_Image *new_img; double direction, nick, tilt, fl; new_img = new Fl_JPEG_Image(file); if (new_img == NULL) return 1; if (img) delete img; img = new_img; if (img_file) free(img_file); img_file = strdup(file); known_hills->clear(); h(img->h()); w(img->w()); // try to retrieve gipfel data from JPEG meta data md->load_image(file); set_view_long(md->longitude()); set_view_lat(md->latitude()); set_view_height(md->height()); projection((ProjectionLSQ::Projection_t) md->projection_type()); have_gipfel_info = true; direction = md->direction(); if (isnan(direction)) { set_center_angle(0.0); have_gipfel_info = false; } else { set_center_angle(direction); } nick = md->nick(); if (isnan(nick)) { set_nick_angle(0.0); have_gipfel_info = false; } else { set_nick_angle(nick); } tilt = md->tilt(); if (isnan(tilt)) { set_tilt_angle(0.0); have_gipfel_info = false; } else { set_tilt_angle(tilt); } fl = md->focal_length_35mm(); if (isnan(fl) || fl == 0.0) { set_focal_length_35mm(35.0); have_gipfel_info = false; } else { set_focal_length_35mm(fl); } // try to get distortion parameters in the following ordering: // 1. gipfel data in JPEG comment // 2. matching distortion profile // 3. set the to 0.0, 0.0 md->distortion_params(&pan->parms.k0, &pan->parms.k1, &pan->parms.x0); if (isnan(pan->parms.k0)) { char buf[1024]; if (get_distortion_profile_name(buf, sizeof(buf)) == 0) load_distortion_params(buf); if (isnan(pan->parms.k0)) pan->parms.k0 = 0.0; if (isnan(pan->parms.k1)) pan->parms.k1 = 0.0; if (isnan(pan->parms.x0)) pan->parms.x0 = 0.0; } return 0; } const char * GipfelWidget::get_image_filename() { return img_file; } int GipfelWidget::save_image(char *file) { if (img_file == NULL) { fprintf(stderr, "Nothing to save\n"); return 1; } md->longitude(get_view_long()); md->latitude(get_view_lat()); md->height(get_view_height()); md->direction(get_center_angle()); md->nick(get_nick_angle()); md->tilt(get_tilt_angle()); md->focal_length_35mm(get_focal_length_35mm()); md->projection_type((int) projection()); md->distortion_params(pan->parms.k0, pan->parms.k1, pan->parms.x0); return md->save_image(img_file, file); } int GipfelWidget::load_data(const char *file) { int r; r = pan->load_data(file); set_labels(pan->get_visible_mountains()); return r; } int GipfelWidget::load_track(const char *file) { if (track_points) { pan->remove_hills(Hill::TRACK_POINT); track_points->clobber(); delete track_points; } track_points = new Hills(); if (track_points->load(file) != 0) { delete track_points; track_points = NULL; return 1; } for (int i = 0; i < track_points->get_num(); i++) track_points->get(i)->flags |= Hill::TRACK_POINT; pan->add_hills(track_points); redraw(); return 0; } int GipfelWidget::set_viewpoint(const char *pos) { int r; r = pan->set_viewpoint(pos); set_labels(pan->get_visible_mountains()); redraw(); return r; } void GipfelWidget::set_viewpoint(const Hill *m) { pan->set_viewpoint(m); set_labels(pan->get_visible_mountains()); redraw(); } static void draw_flag(int x, int y) { fl_polygon(x, y - 10, x, y - 20, x + 10, y - 15); fl_yxline(x, y, y - 10); fl_circle(x , y, 3); } void GipfelWidget::draw() { Hills *mnts; Hill *m; int i, height; if (img == NULL) return; fl_push_clip(x(), y(), w(), h()); img->draw(x(),y(),w(),h(),0,0); /* hills */ fl_font(FL_HELVETICA, 8); mnts = pan->get_visible_mountains(); fl_color(FL_YELLOW); height = fl_height(); for (i=0; iget_num(); i++) { m = mnts->get(i); int m_x = w() / 2 + x() + (int) rint(m->x); int m_y = h() / 2 + y() + (int) rint(m->y); if ((m->flags & (Hill::DUPLIC|Hill::TRACK_POINT)) || (!show_hidden && (m->flags & Hill::HIDDEN)) || known_hills->contains(m) || m == focused_mountain) continue; if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2) continue; fl_xyline(m_x - CROSS_SIZE, m_y, m_x + CROSS_SIZE); fl_yxline(m_x, m_y + m->label_y - height, m_y + CROSS_SIZE); fl_xyline(m_x, m_y + m->label_y - height, m_x + m->label_x); } for (i=0; iget_num(); i++) { m = mnts->get(i); int m_x = w() / 2 + x() + (int) rint(m->x); int m_y = h() / 2 + y() + (int) rint(m->y); if ((m->flags & (Hill::DUPLIC|Hill::TRACK_POINT)) || (!show_hidden && (m->flags & Hill::HIDDEN)) || m == focused_mountain) continue; if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2) continue; if (known_hills->contains(m)) { if (known_hills->get_num() > 3) fl_color(FL_GREEN); else fl_color(FL_RED); draw_flag(m_x, m_y); fl_color(FL_BLACK); } else if (m->flags & Hill::HIDDEN) { fl_color(FL_BLUE); } else { fl_color(FL_BLACK); } fl_draw(m->name, m_x + 2, m_y + m->label_y); } if (focused_mountain) { m = focused_mountain; int m_x = w() / 2 + x() + (int) rint(m->x); int m_y = h() / 2 + y() + (int) rint(m->y); fl_color(FL_YELLOW); fl_rectf(m_x, m_y - height, (int) fl_width(focused_mountain_label) + 2, height + 2); fl_color(FL_BLACK); fl_draw(focused_mountain_label, m_x, m_y); } /* track */ if (track_points && track_points->get_num() > 0) { int last_x = 0, last_y = 0, last_initialized = 0; for (i=1; iget_num(); i++) { m = track_points->get(i); int m_x = w() / 2 + x() + (int) rint(m->x); int m_y = h() / 2 + y() + (int) rint(m->y); if (!(track_points->get(i)->flags & Hill::VISIBLE)) continue; if (track_points->get(i)->flags & Hill::HIDDEN) fl_color(FL_BLUE); else fl_color(FL_RED); fl_line_style(FL_SOLID | FL_CAP_ROUND | FL_JOIN_ROUND, get_rel_track_width(track_points->get(i))); if (last_initialized) { fl_begin_line(); fl_vertex(last_x, last_y); fl_vertex(m_x, m_y); fl_end_line(); } last_x = m_x; last_y = m_y; last_initialized++; } fl_line_style(0); } fl_pop_clip(); } static int overlap(double x1, double l1, double x2, double l2) { return x1 <= x2 + l2 && x1 + l1 >= x2; } void GipfelWidget::set_labels(Hills *v) { int height; if (!img) return; fl_font(FL_HELVETICA, 8); height = fl_height(); for (int i = 0; i < v->get_num(); i++) { Hill *m = v->get(i); Hills colliding; if (m->flags & (Hill::DUPLIC | Hill::TRACK_POINT)) continue; if (!show_hidden && (m->flags & Hill::HIDDEN)) continue; if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2) continue; m->label_x = (int) fl_width(m->name) + 1; m->label_y = 0; for (int j = i - 1; j >= 0; j--) { Hill *n = v->get(j); if (n->flags & (Hill::DUPLIC | Hill::TRACK_POINT)) continue; if (!show_hidden && (n->flags & Hill::HIDDEN)) continue; if (fabs(m->x) > img->w() / 2 || fabs(m->y) > img->h() / 2) continue; if (overlap(m->x, m->label_x, n->x - CROSS_SIZE, n->label_x)) colliding.add(n); else break; } colliding.sort(Hills::SORT_LABEL_Y); for (int j = 0; j < colliding.get_num(); j++) { Hill *n = colliding.get(j); // Check for overlapping labels and // overlaps between labels and peak markers if (overlap(m->y + m->label_y - height, height, n->y + n->label_y - height, height) || overlap(m->y + m->label_y - height, height, n->y - CROSS_SIZE, 2 * CROSS_SIZE)) { m->label_y = (int) rint(n->y + n->label_y - m->y - height - 2); } } } } Hill * GipfelWidget::find_mountain(Hills *mnts, int m_x, int m_y) { Hill *m; int center_x = w() / 2; int center_y = h() / 2; for (int i = 0; i < mnts->get_num(); i++) { m = mnts->get(i); if (m_x - center_x >= m->x - 2 && m_x - center_x < m->x + 2 && m_y - center_y >= m->y - 2 && m_y - center_y < m->y + 2) return m; } return NULL; } int GipfelWidget::toggle_known_mountain(int m_x, int m_y) { Hills *mnts = pan->get_visible_mountains(); int center_x = w() / 2; int center_y = h() / 2; for (int i = 0; i < mnts->get_num(); i++) { Hill *m = mnts->get(i); if (m->flags & (Hill::DUPLIC | Hill::TRACK_POINT)) continue; if (m_x - center_x >= m->x - 2 && m_x - center_x < m->x + 2 && m_y - center_y >= m->y - 2 && m_y - center_y < m->y + 2) { if (known_hills->contains(m)) known_hills->remove(m); else known_hills->add(m); redraw(); return 0; } } cur_mountain = NULL; redraw(); return 1; } int GipfelWidget::set_mountain(int m_x, int m_y) { int old_x, old_y, old_label_y; int center_x = w() / 2; int center_y = h() / 2; if (!cur_mountain) return 1; old_x = (int) rint(cur_mountain->x); old_y = (int) rint(cur_mountain->y); old_label_y = cur_mountain->label_y; cur_mountain->x = m_x - center_x; cur_mountain->y = m_y - center_y; cur_mountain->label_y = 0; damage(4, center_x + x() + old_x - 2*CROSS_SIZE - 1, center_y + y() + old_y + old_label_y - 2*CROSS_SIZE - 20, std::max(20, cur_mountain->label_x) + 2*CROSS_SIZE + 4, std::max(20, old_label_y) + 22 ); damage(4, (int) rint(center_x + x() + cur_mountain->x - 2*CROSS_SIZE - 1), (int) rint(center_y + y() + cur_mountain->y + cur_mountain->label_y - 2*CROSS_SIZE - 20), std::max(20, cur_mountain->label_x) + 2*CROSS_SIZE + 4, std::max(20, cur_mountain->label_y) + 22 ); return 0; } void GipfelWidget::set_center_angle(double a) { pan->set_center_angle(a); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_nick_angle(double a) { pan->set_nick_angle(a); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_tilt_angle(double a) { pan->set_tilt_angle(a); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_focal_length_35mm(double s) { int w = std::max(img->w(), img->h()); // assume sensor is wider than high pan->set_scale(s * (double) w / 35.0); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::projection(ProjectionLSQ::Projection_t p) { pan->set_projection(p); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_distortion_params(double k0, double k1, double x0) { pan->set_distortion_params(k0, k1, x0); redraw(); } double GipfelWidget::get_focal_length_35mm() { int w; if (img == NULL) return NAN; w = std::max(img->w(), img->h()); // assume sensor is wider than high return pan->get_scale() * 35.0 / (double) w; } void GipfelWidget::find_peak_cb(Fl_Widget *, void *f) { GipfelWidget *g = (GipfelWidget*) f; Hill *m = choose_hill(g->pan->get_close_mountains(), "Find Peak"); if (m) { if (!g->known_hills->contains(m)) g->known_hills->add(m); m->flags |= Hill::VISIBLE; if (! g->pan->get_visible_mountains()->contains(m)) g->pan->get_visible_mountains()->add(m); g->set_labels(g->pan->get_visible_mountains()); g->cur_mountain = m; g->set_mountain(g->mouse_x, g->mouse_y); g->comp_params(); } } void GipfelWidget::toggle_hidden_cb(Fl_Widget *, void *f) { GipfelWidget *g = (GipfelWidget*) f; Hill *m = g->find_mountain(g->pan->get_visible_mountains(), g->mouse_x, g->mouse_y); if (!m) return; if (m->flags & Hill::HIDDEN) m->flags &= ~Hill::HIDDEN; else m->flags |= Hill::HIDDEN; g->set_labels(g->pan->get_visible_mountains()); g->redraw(); } void GipfelWidget::set_height_dist_ratio(double r) { pan->set_height_dist_ratio(r); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_hide_value(double h) { pan->set_hide_value(h); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_show_hidden(bool h) { show_hidden = h; set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_view_lat(double v) { pan->set_view_lat(v); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_view_long(double v) { pan->set_view_long(v); set_labels(pan->get_visible_mountains()); redraw(); } void GipfelWidget::set_view_height(double v) { pan->set_view_height(v); set_labels(pan->get_visible_mountains()); redraw(); } int GipfelWidget::comp_params() { int ret; fl_cursor(FL_CURSOR_WAIT); ret = pan->comp_params(known_hills); set_labels(pan->get_visible_mountains()); redraw(); fl_cursor(FL_CURSOR_DEFAULT); if (params_changed_cb) params_changed_cb(); return ret; } int GipfelWidget::get_rel_track_width(Hill *m) { double dist = pan->get_real_distance(m); return (int) rint(std::max((pan->get_scale()*track_width)/(dist*10.0), 1.0)); } void GipfelWidget::set_track_width(double w) { track_width = w; redraw(); } int GipfelWidget::handle(int event) { Hill *m; switch(event) { case FL_PUSH: mouse_x = Fl::event_x()-x(); mouse_y = Fl::event_y()-y(); if (Fl::event_button() == FL_LEFT_MOUSE) { m = find_mountain(known_hills, mouse_x, mouse_y); if (m) cur_mountain = m; } else if (Fl::event_button() == FL_MIDDLE_MOUSE) { toggle_known_mountain(mouse_x, mouse_y); if (focused_mountain) { focused_mountain = NULL; redraw(); } } else if (Fl::event_button() == FL_RIGHT_MOUSE) { focused_mountain = NULL; Fl_Menu_Button rclick_menu(Fl::event_x_root(), Fl::event_y_root(), 80, 1); Hill *m = find_mountain(pan->get_visible_mountains(), mouse_x, mouse_y); char buf[1024]; rclick_menu.add("Find Peak", 0, find_peak_cb, this); if (m) { if (m->flags & Hill::HIDDEN) snprintf(buf, sizeof(buf), "Unhide %s", m->name); else snprintf(buf, sizeof(buf), "Hide %s", m->name); rclick_menu.add(buf, 0, toggle_hidden_cb, this); } rclick_menu.popup(); } Fl::focus(this); return 1; case FL_DRAG: set_mountain(Fl::event_x()-x(), Fl::event_y()-y()); return 1; case FL_RELEASE: cur_mountain = NULL; if (known_hills->get_num() > 0) comp_params(); return 1; case FL_ENTER: return 1; case FL_LEAVE: return 1; case FL_MOVE: m = find_mountain(pan->get_visible_mountains(), Fl::event_x()-x(), Fl::event_y()-y()); if (m != focused_mountain && (!m || !known_hills->contains(m))) { if (m) snprintf(focused_mountain_label, sizeof(focused_mountain_label) - 1, "%s (%dm), distance %.2fkm", m->name, (int) m->height, pan->get_real_distance(m) / 1000.0); focused_mountain = m; redraw(); } return 1; case FL_FOCUS: return 1; case FL_UNFOCUS: return 0; } return 0; } int GipfelWidget::export_hills(const char *file, FILE *fp) { Hills export_hills, *mnts; if (!have_gipfel_info) { fprintf(stderr, "No gipfel info available for %s.\n", img_file); return 0; } if (file) { if (export_hills.load(file) != 0) return 1; for (int i = 0; i < export_hills.get_num(); i++) export_hills.get(i)->flags |= Hill::EXPORT; pan->add_hills(&export_hills); } fprintf(fp, "#\n# name\theight\tx\ty\tdistance\tflags\n#\n"); mnts = pan->get_visible_mountains(); for (int i = 0; i < mnts->get_num(); i++) { Hill *m = mnts->get(i); int _x = (int) rint(m->x) + w() / 2; int _y = (int) rint(m->y) + h() / 2; if (m->flags & Hill::DUPLIC || m->flags & Hill::HIDDEN || (file && !(m->flags & Hill::EXPORT))) { continue; } if (_x < 0 || _x > w() || _y < 0 || _y > h()) continue; fprintf(fp, "%s\t%d\t%d\t%d\t%d\n", m->name, (int) rint(m->height), _x, _y, (int) rint(pan->get_real_distance(m))); } pan->remove_hills(Hill::EXPORT); return 0; } int GipfelWidget::get_pixel(ScanImage::mode_t m, double a_alph, double a_nick, int *r, int *g, int *b) { double px, py; int ret; if (img == NULL) return 1; if (pan->get_coordinates(a_alph, a_nick, &px, &py) != 0) return 1; ret = ScanImage::get_pixel(img, m, px + ((double) img->w()) / 2.0, py + ((double) img->h()) / 2.0, r, g, b); return ret; } int GipfelWidget::get_distortion_profile_name(char *buf, int buflen) { int n; if (md && md->manufacturer() && md->model()) { n = snprintf(buf, buflen, "%s_%s_%.2f_mm", md->manufacturer(), md->model(), md->focal_length()); return n > buflen; } else { return 1; } } static Fl_Preferences dist_prefs(Fl_Preferences::USER, "Johannes.HofmannATgmx.de", "gipfel/DistortionProfiles"); int GipfelWidget::load_distortion_params(const char *prof_name) { int ret = 0; Fl_Preferences prof(dist_prefs, prof_name); ret += prof.get("k0", pan->parms.k0, pan->parms.k0); ret += prof.get("k1", pan->parms.k1, pan->parms.k1); ret += prof.get("x0", pan->parms.x0, pan->parms.x0); return !ret; } int GipfelWidget::save_distortion_params(const char *prof_name, int force) { Fl_Preferences prof(dist_prefs, prof_name); if (!force && prof.entryExists("k0")) return 1; prof.set("k0", pan->parms.k0); prof.set("k1", pan->parms.k1); prof.set("x0", pan->parms.x0); return 0; }