/* * * XASTIR, Amateur Station Tracking and Information Reporting * Copyright (C) 1999,2000 Frank Giannandrea * Copyright (C) 2000-2019 The Xastir Group * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Look at the README for more information on the program. */ //#define MAP_SCALE_CHECK #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H #include "snprintf.h" #include #include #include #include #include #include #include #include // Needed for Solaris #ifdef HAVE_STRINGS_H #include #endif // HAVE_STRINGS_H #ifdef HAVE_MAGICK #if TIME_WITH_SYS_TIME #include #include #else // TIME_WITH_SYS_TIME #if HAVE_SYS_TIME_H #include #else // HAVE_SYS_TIME_H #include #endif // HAVE_SYS_TIME_H #endif // TIME_WITH_SYS_TIME #undef RETSIGTYPE // TVR: "stupid ImageMagick" // The problem is that magick/api.h includes Magick's config.h file, and that // pulls in all the same autoconf-generated defines that we use. // plays those games below, but I don't think in the end that they actually // make usable macros with our own data in them. // Fortunately, we don't need them, so I'll just undef the ones that are // causing problems today. See main.c for fixes that preserve our values. #undef PACKAGE #undef VERSION /* JMT - stupid ImageMagick */ #define XASTIR_PACKAGE_BUGREPORT PACKAGE_BUGREPORT #undef PACKAGE_BUGREPORT #define XASTIR_PACKAGE_NAME PACKAGE_NAME #undef PACKAGE_NAME #define XASTIR_PACKAGE_STRING PACKAGE_STRING #undef PACKAGE_STRING #define XASTIR_PACKAGE_TARNAME PACKAGE_TARNAME #undef PACKAGE_TARNAME #define XASTIR_PACKAGE_VERSION PACKAGE_VERSION #undef PACKAGE_VERSION #ifdef HAVE_MAGICK #ifdef HAVE_MAGICKCORE_MAGICKCORE_H #include #else #ifdef HAVE_MAGICK_API_H #include #endif // HAVE_MAGICK_API_H #endif //HAVE_MAGICKCORE_MAGICKCORE_H #endif //HAVE_MAGICK #undef PACKAGE_BUGREPORT #define PACKAGE_BUGREPORT XASTIR_PACKAGE_BUGREPORT #undef XASTIR_PACKAGE_BUGREPORT #undef PACKAGE_NAME #define PACKAGE_NAME XASTIR_PACKAGE_NAME #undef XASTIR_PACKAGE_NAME #undef PACKAGE_STRING #define PACKAGE_STRING XASTIR_PACKAGE_STRING #undef XASTIR_PACKAGE_STRING #undef PACKAGE_TARNAME #define PACKAGE_TARNAME XASTIR_PACKAGE_TARNAME #undef XASTIR_PACKAGE_TARNAME #undef PACKAGE_VERSION #define PACKAGE_VERSION XASTIR_PACKAGE_VERSION #undef XASTIR_PACKAGE_VERSION #endif // HAVE_MAGICK #include #include #include #ifdef HAVE_X11_XPM_H #include #ifdef HAVE_LIBXPM // if we have both, prefer the extra library #undef HAVE_XM_XPMI_H #endif // HAVE_LIBXPM #endif // HAVE_X11_XPM_H #ifdef HAVE_XM_XPMI_H #include #endif // HAVE_XM_XPMI_H #include #include #include "xastir.h" #include "maps.h" #include "alert.h" #include "util.h" #include "main.h" #include "datum.h" #include "draw_symbols.h" #include "rotated.h" #include "color.h" #include "xa_config.h" // Must be last include file #include "leak_detection.h" extern XmFontList fontlist1; // Menu/System fontlist #define GRID_MORE 5000 #define CHECKMALLOC(m) if (!m) { fprintf(stderr, "***** Malloc Failed *****\n"); exit(0); } // Check for XPM and/or ImageMagick. We use "NO_GRAPHICS" // to disable some routines below if the support for them // is not compiled in. #if !(defined(HAVE_LIBXPM) || defined(HAVE_LIBXPM_IN_XM) || defined(HAVE_MAGICK)) #define NO_GRAPHICS 1 #endif // !(HAVE_LIBXPM || HAVE_LIBXPM_IN_XM || HAVE_MAGICK) #if !(defined(HAVE_LIBXPM) || defined(HAVE_LIBXPM_IN_XM)) #define NO_XPM 1 #endif // !(HAVE_LIBXPM || HAVE_LIBXPM_IN_XM) void draw_rotated_label_text_to_target (Widget w, int rotation, int x, int y, int label_length, int color, char *label_text, int fontsize, Pixmap target_pixmap, int draw_outline, int outline_bg_color); int get_rotated_label_text_height_pixels(Widget w, char *label_text, int fontsize); // Constants defining the color of the labeled grid border. int outline_border_labels = TRUE; // if true put an outline around the border labels int border_foreground_color = 0x20; // color of the map border, if shown // 0x08 is black. // 0x20 is white. int outline_border_labels_color = 0x20; // outline_border_labels_color = border_foreground_color; // color of outline to draw around border labels // use color of border to help make text more legible. // Print options Widget print_properties_dialog = (Widget)NULL; static xastir_mutex print_properties_dialog_lock; Widget print_postscript_dialog = (Widget)NULL; static xastir_mutex print_postscript_dialog_lock; Widget printer_data; Widget previewer_data; Widget rotate_90 = (Widget)NULL; Widget auto_rotate = (Widget)NULL; //char print_paper_size[20] = "Letter"; // Displayed in dialog, but not used yet. int print_rotated = 0; int print_auto_rotation = 0; //float print_scale = 1.0; // Not used yet. int print_auto_scale = 0; //int print_blank_background_color = 0; // Not used yet. int print_in_monochrome = 0; int print_resolution = 150; // 72 dpi is normal for Postscript. // 100 or 150 dpi work well with HP printer int print_invert = 0; // Reverses black/white char printer_program[MAX_FILENAME+1]; char previewer_program[MAX_FILENAME+1]; time_t last_snapshot = 0; // Used to determine when to take next snapshot time_t last_kmlsnapshot = 0; // Used to determine when to take next kml snapshot int doing_snapshot = 0; int snapshot_interval = 0; int mag; int npoints; /* number of points in a line */ float raster_map_intensity = 0.65; // Raster map color intensity, set from Maps->Map Intensity float imagemagick_gamma_adjust = 0.0; // Additional imagemagick map gamma correction, set from Maps->Adjust Gamma // Storage for the index file timestamp time_t map_index_timestamp; int grid_size = 0; // Rounding #ifndef HAVE_ROUNDF // Poor man's rounding, but rounds away from zero as roundf is supposed to. float roundf(float x) { int i; i= (((x)>=0)?(((x)+.5)):(((x)-.5))); return ((float)i); } #endif // UTM Grid stuff // //#define UTM_DEBUG //#define UTM_DEBUG_VERB //#define UTM_DEBUG_ALLOC // static inline int max_i(int a, int b) { return (a > b ? a : b); } #define UTM_GRID_EQUATOR 10000000 // the maximum number of UTM zones that will appear on a screen that has a // high enough resolution to display the within-zone utm grid #define UTM_GRID_MAX_ZONES 4 // the maximum number of grid lines in each direction shown in each zone // on the screen at one time #define UTM_GRID_MAX_COLS_ROWS 64 #define UTM_GRID_DEF_NALLOCED 8 #define UTM_GRID_RC_EMPTY 0xffff // the hash stores the upper left and lower right boundaries of the // display of a utm grid to determine whether it matches the current // screen boundaries or whether the grid needs to be recalculated // for the current screen. typedef struct { long ul_x; long ul_y; long lr_x; long lr_y; } hash_t; // The utm grid is drawn by connecting a grid of points marking the // intersection of the easting and northing lines. col_or_row holds // this grid of points for the portion of a utm zone visible on the // screen. Zone boundaries are not directly represented here. // In particular, the parallels separating lettered zone rows are // not directly represented here. typedef struct { int firstpoint; int npoints; int nalloced; XPoint *points; } col_or_row_t; // The portion of a utm zone visible on the screen has some number of // visible easting lines and northing lines forming a grid that covers // part or all of the screen. Zone holds arrays of the coordinates // of the visible easting and northing intersections, and the next set // of intersections off screen. The coarseness of this grid is // determined by the current scale and stored in utm_grid_spacing_m. typedef struct { unsigned int ncols; col_or_row_t col[UTM_GRID_MAX_COLS_ROWS]; unsigned int nrows; col_or_row_t row[UTM_GRID_MAX_COLS_ROWS]; int boundary_x; } zone_t; // A utm grid is represented by its hash (upper left and lower right coordinates) // which are used to check whether or not it fits the current screen or needs to // be recalculated, and an array of the zones visible on the screen, each containing // arrays of the points marking the easting/northing intersections of a zone (with // one or more lettered zone rows). typedef struct { hash_t hash; unsigned int nzones; zone_t zone[UTM_GRID_MAX_ZONES]; } utm_grid_t; utm_grid_t utm_grid; unsigned int utm_grid_spacing_m; // ^ the above is extern'ed in maps.h for use by draw_ruler_text in // db.c // Clear out/set up the UTM/MGRS minor grid intersection arrays. // // do_alloc = 0: Don't allocate memory // 1: Allocate memory for the points // // Returns: 0 if ok // 1 if malloc problem // int utm_grid_clear(int do_alloc) { int i, j; utm_grid.hash.ul_x = 0; utm_grid.hash.ul_y = 0; utm_grid.hash.lr_x = 0; utm_grid.hash.lr_y = 0; utm_grid.nzones = 0; for (i=0; i < UTM_GRID_MAX_ZONES; i++) { utm_grid.zone[i].ncols = utm_grid.zone[i].nrows = 0; utm_grid.zone[i].boundary_x = 0; for (j=0; j < UTM_GRID_MAX_COLS_ROWS; j++) { // // Clear out column arrays // utm_grid.zone[i].col[j].firstpoint = UTM_GRID_RC_EMPTY; utm_grid.zone[i].col[j].npoints = 0; if (utm_grid.zone[i].col[j].nalloced) { free(utm_grid.zone[i].col[j].points); } utm_grid.zone[i].col[j].nalloced = 0; utm_grid.zone[i].col[j].points = NULL; if (do_alloc) { // Allocate enough space for 8 points utm_grid.zone[i].col[j].points = calloc(UTM_GRID_DEF_NALLOCED, sizeof(XPoint)); if (!utm_grid.zone[i].col[j].points) { fprintf(stderr,"calloc(%d, %d) for z=%d col=%d FAILED!\n", UTM_GRID_DEF_NALLOCED, (int)sizeof(XPoint), i, j); // abort(); // Causes a segfault return(1); } utm_grid.zone[i].col[j].nalloced = UTM_GRID_DEF_NALLOCED; } // // Clear out row arrays // utm_grid.zone[i].row[j].firstpoint = UTM_GRID_RC_EMPTY; utm_grid.zone[i].row[j].npoints = 0; utm_grid.zone[i].row[j].nalloced = 0; if (utm_grid.zone[i].row[j].nalloced) { free(utm_grid.zone[i].row[j].points); } utm_grid.zone[i].row[j].nalloced = 0; utm_grid.zone[i].row[j].points = NULL; if (do_alloc) { // Allocate enough space for 8 points utm_grid.zone[i].row[j].points = calloc(UTM_GRID_DEF_NALLOCED, sizeof(XPoint)); if (!utm_grid.zone[i].row[j].points) { fprintf(stderr,"calloc(%d, %d) for z=%d row=%d FAILED!\n", UTM_GRID_DEF_NALLOCED, (int)sizeof(XPoint), i, j); // abort(); // Causes a segfault return(1); } utm_grid.zone[i].row[j].nalloced = UTM_GRID_DEF_NALLOCED; } } } return(0); } void maps_init(void) { fprintf(stderr,"\n\nBuilt-in map types:\n"); fprintf(stderr,"%10s USGS GNIS Datapoints\n","gnis"); fprintf(stderr,"%10s USGS GNIS Datapoints w/population\n","pop"); fprintf(stderr,"%10s APRSdos Maps\n","map"); fprintf(stderr,"%10s WinAPRS/MacAPRS/X-APRS Maps\n","map"); fprintf(stderr,"\nSupport for these additional map types has been compiled in: \n"); #ifdef HAVE_MAGICK fprintf(stderr,"%10s Image Map (ImageMagick/GraphicsMagick library, many formats allowed)\n","geo"); #endif // HAVE_MAGICK #ifndef NO_GRAPHICS #ifdef HAVE_LIBCURL fprintf(stderr,"%10s URL (Internet maps via libcurl library)\n","geo"); fprintf(stderr,"%10s URL (OpenStreetMaps via libcurl library\n","geo"); fprintf(stderr,"%10s Copyright OpenStreetMap and contributors, CC-BY-SA)\n", ""); #else #ifdef HAVE_WGET fprintf(stderr,"%10s URL (Internet maps via wget)\n","geo"); fprintf(stderr,"%10s URL (OpenStreetMaps via wget\n","geo"); fprintf(stderr,"%10s Copyright OpenStreetMap and contributors, CC-BY-SA)\n", ""); #endif // HAVE_WGET #endif // HAVE_LIBCURL #endif // NO_GRAPHICS #ifdef HAVE_LIBSHP fprintf(stderr,"%10s ESRI Shapefile Maps (Shapelib library)\n","shp"); #endif // HAVE_LIBSHP #ifdef HAVE_LIBGEOTIFF fprintf(stderr,"%10s USGS DRG Geotiff Topographic Maps (libgeotiff/libproj)\n","tif"); #endif // HAVE_LIBGEOTIFF #ifndef NO_XPM fprintf(stderr,"%10s X Pixmap Maps (XPM library)\n","xpm"); #endif // NO_XPM init_critical_section( &print_properties_dialog_lock ); init_critical_section( &print_postscript_dialog_lock ); // Clear the minor UTM/MGRS grid arrays. Do _not_ allocate // memory for the points. (void)utm_grid_clear(0); } /* * Calculate NS distance scale at a given location * in meters per Xastir unit */ double calc_dscale_y(long UNUSED(x), long UNUSED(y) ) { // approximation by looking at +/- 0.5 minutes offset // (void)(calc_distance(y-3000, x, y+3000, x)/6000.0); // but this scale is fixed at 1852/6000 return((double)(1852.0/6000.0)); } /* * Calculate EW distance scale at a given location * in meters per Xastir unit */ double calc_dscale_x(long x, long y) { // approximation by looking at +/- 0.5 minutes offset // we should find a better formula... return(calc_distance(y, x-3000, y, x+3000)/6000.0); } /* * Calculate x map scaling for current location * With that we could have equal distance scaling or a better * view for pixel maps */ long get_x_scale(long x, long y, long ysc) { long xsc; double sc_x; double sc_y; sc_x = calc_dscale_x(x,y); // meter per Xastir unit sc_y = calc_dscale_y(x,y); if (sc_x < 0.01 || ysc > 50000) // keep it near the poles (>88 deg) or if big parts of world seen { xsc = ysc; } else // adjust y scale, so that the distance is identical in both directions: { xsc = (long)(ysc * sc_y / sc_x +0.4999); } //fprintf(stderr,"Scale: x %5.3fkm/deg, y %5.3fkm/deg, x %ld y %ld\n",sc_x*360,sc_y*360,xsc,ysc); return(xsc); } /** MAP DRAWING ROUTINES **/ // Function to perform 2D line-clipping Using the improved parametric // line-clipping algorithm by Liang, Barsky, and Slater published in // the paper: "Some Improvements to a Parametric Line Clipping // Algorithm", 1992. Called by clip2d() function below. This // function is set up for float values. See the clipt_long() and // clipt_int() functions for use with other types of values. // // Returns False if the line is rejected, True otherwise. May modify // t0 and t1. // int clipt(float p, float q, float *t0, float *t1) { float r; int accept = True; if (p < 0) // Entering visible region, so compute t0 { if (q < 0) // t0 will be non-negative, so continue { r = q / p; if (r > *t1) // t0 will exceed t1, so reject { accept = False; } else if (r > *t0) // t0 is max of r's { *t0 = r; } } } else { if (p > 0) // Exiting visible region, so compute t1 { if (q < p) // t1 will be <= 1, so continue { r = q / p; if (r < *t0) // t1 will be <= t0, so reject { accept = False; } else if (r < *t1) // t1 is min of r's { *t1 = r; } } } else // p == 0 { if (q < 0) // Line parallel and outside, so reject { accept = False; } } } return(accept); } // Function to perform 2D line-clipping using the improved parametric // line-clipping algorithm by Liang, Barsky, and Slater published in // the paper: "Some Improvements to a Parametric Line Clipping // Algorithm", 1992. Uses the clipt() function above. // // Returns False if the line is rejected, True otherwise. x0, y0, // x1, y1 may get modified by this function. These will be the new // clipped line ends that fit inside the window. // // Clip 2D line segment with endpoints (x0,y0) and (x1,y1). The clip // window is x_left <= x <= x_right and y_bottom <= y <= y_top. // // This function is set up for float values. See the clip2d_long() // and clip2d_screen() functions for use with other types of values. // int clip2d(float *x0, float *y0, float *x1, float *y1) { float t0, t1; float delta_x, delta_y; int visible = False; float x_left, y_top; // NW corner of screen float x_right, y_bottom; // SE corner of screen // Assign floating point values for screen corners y_top = f_NW_corner_latitude; y_bottom = f_SE_corner_latitude; x_left = f_NW_corner_longitude; x_right = f_SE_corner_longitude; if ( ((*x0 < x_left) && (*x1 < x_left)) || ((*x0 > x_right) && (*x1 > x_right)) || ((*y0 < y_bottom) && (*y1 < y_bottom)) || ((*y0 > y_top) && (*y1 > y_top)) ) { // Both endpoints are on outside and same side of visible // region, so reject. return(visible); } t0 = 0; t1 = 1; delta_x = *x1 - *x0; if ( clipt(-delta_x, *x0 - x_left, &t0, &t1) ) // left { if ( clipt(delta_x, x_right - *x0, &t0, &t1) ) // right { delta_y = *y1 - *y0; if ( clipt(-delta_y, *y0 - y_bottom, &t0, &t1) ) // bottom { if ( clipt(delta_y, y_top - *y0, &t0, &t1) ) // top { // Compute coordinates if (t1 < 1) // Compute V1' (new x1,y1) { *x1 = *x0 + t1 * delta_x; *y1 = *y0 + t1 * delta_y; } if (t0 > 0) // Compute V0' (new x0,y0) { *x0 = *x0 + t0 * delta_x; *y0 = *y0 + t0 * delta_y; } visible = True; } } } } return(visible); } // Function to perform 2D line-clipping Using the improved parametric // line-clipping algorithm by Liang, Barsky, and Slater published in // the paper: "Some Improvements to a Parametric Line Clipping // Algorithm", 1992. Called by clip2d_long() function below. This // function is set up for Xastir coordinate values (unsigned longs). // See the clipt() and clipt_int() functions for use with other // types of values. // // Returns False if the line is rejected, True otherwise. May modify // t0 and t1. // int clipt_long(long p, long q, float *t0, float *t1) { float r; int accept = True; if (p < 0) // Entering visible region, so compute t0 { if (q < 0) // t0 will be non-negative, so continue { r = q / (p * 1.0); if (r > *t1) // t0 will exceed t1, so reject { accept = False; } else if (r > *t0) // t0 is max of r's { *t0 = r; } } } else { if (p > 0) // Exiting visible region, so compute t1 { if (q < p) // t1 will be <= 1, so continue { r = q / (p * 1.0); if (r < *t0) // t1 will be <= t0, so reject { accept = False; } else if (r < *t1) // t1 is min of r's { *t1 = r; } } } else // p == 0 { if (q < 0) // Line parallel and outside, so reject { accept = False; } } } //fprintf(stderr,"clipt_long: %d\n",accept); return(accept); } // Function to perform 2D line-clipping using the improved parametric // line-clipping algorithm by Liang, Barsky, and Slater published in // the paper: "Some Improvements to a Parametric Line Clipping // Algorithm", 1992. Uses the clipt_long() function above. // // Returns False if the line is rejected, True otherwise. x0, y0, // x1, y1 may get modified by this function. These will be the new // clipped line ends that fit inside the window. // // Clip 2D line segment with endpoints (x0,y0) and (x1,y1). The clip // window is x_left <= x <= x_right and y_bottom <= y <= y_top. // // This function uses the Xastir coordinate system. We had to flip // y_bottom/y_top below due to the coordinate system being // upside-down. // // This function is set up for Xastir coordinate values (unsigned // longs). See the clip2d() or clip2d_screen() functions for use // with other types of values. // int clip2d_long(unsigned long *x0, unsigned long *y0, unsigned long *x1, unsigned long *y1) { float t0, t1; long delta_x, delta_y; int visible = False; unsigned long x_left = NW_corner_longitude; unsigned long x_right = SE_corner_longitude; // Reverse the following two as our Xastir coordinate system is // upside down. The algorithm requires this order. unsigned long y_top = SE_corner_latitude; unsigned long y_bottom = NW_corner_latitude; if ( ( (*x0 < x_left ) && (*x1 < x_left ) ) || ( (*x0 > x_right ) && (*x1 > x_right ) ) || ( (*y0 < y_bottom) && (*y1 < y_bottom) ) || ( (*y0 > y_top ) && (*y1 > y_top ) ) ) { // Both endpoints are on same side of visible region and // outside of it, so reject. //fprintf(stderr,"reject 1\n"); return(visible); } t0 = 0; t1 = 1; delta_x = *x1 - *x0; if ( clipt_long(-delta_x, *x0 - x_left, &t0, &t1) ) // left { if ( clipt_long(delta_x, x_right - *x0, &t0, &t1) ) // right { delta_y = *y1 - *y0; if ( clipt_long(-delta_y, *y0 - y_bottom, &t0, &t1) ) // bottom { if ( clipt_long(delta_y, y_top - *y0, &t0, &t1) ) // top { // Compute coordinates if (t1 < 1) // Compute V1' (new x1,y1) { *x1 = *x0 + t1 * delta_x; *y1 = *y0 + t1 * delta_y; } if (t0 > 0) // Compute V0' (new x0,y0) { *x0 = *x0 + t0 * delta_x; *y0 = *y0 + t0 * delta_y; } visible = True; } else { //fprintf(stderr,"reject top\n"); } } else { //fprintf(stderr,"reject bottom\n"); } } else { //fprintf(stderr,"reject right\n"); } } else { //fprintf(stderr,"reject left\n"); } return(visible); } // Function to perform 2D line-clipping Using the improved parametric // line-clipping algorithm by Liang, Barsky, and Slater published in // the paper: "Some Improvements to a Parametric Line Clipping // Algorithm", 1992. Called by clip2d_screen() function below. This // function is set up for screen coordinate values (unsigned ints). // See the clipt() and clipt_long functions for use with other types // of values. // // Returns False if the line is rejected, True otherwise. May modify // t0 and t1. // int clipt_int(int p, int q, float *t0, float *t1) { float r; int accept = True; if (p < 0) // Entering visible region, so compute t0 { if (q < 0) // t0 will be non-negative, so continue { r = q / (p * 1.0); if (r > *t1) // t0 will exceed t1, so reject { accept = False; } else if (r > *t0) // t0 is max of r's { *t0 = r; } } } else { if (p > 0) // Exiting visible region, so compute t1 { if (q < p) // t1 will be <= 1, so continue { r = q / (p * 1.0); if (r < *t0) // t1 will be <= t0, so reject { accept = False; } else if (r < *t1) // t1 is min of r's { *t1 = r; } } } else // p == 0 { if (q < 0) // Line parallel and outside, so reject { accept = False; } } } //fprintf(stderr,"clipt_int: %d\n",accept); return(accept); } // Function to perform 2D line-clipping using the improved parametric // line-clipping algorithm by Liang, Barsky, and Slater published in // the paper: "Some Improvements to a Parametric Line Clipping // Algorithm", 1992. Uses the clipt_int() function above. // // Returns False if the line is rejected, True otherwise. x0, y0, // x1, y1 may get modified by this function. These will be the new // clipped line ends that fit inside the window. // // Clip 2D line segment with endpoints (x0,y0) and (x1,y1). The clip // window is x_left <= x <= x_right and y_bottom <= y <= y_top. // // This function uses the screen coordinate system. We had to flip // y_bottom/y_top below due to the coordinate system being // upside-down. // // This function is set up for screen coordinate values (unsigned // ints). See the clip2d() and clip2d_long() functions for use // with other types of values. // int clip2d_screen(unsigned int *x0, unsigned int *y0, unsigned int *x1, unsigned int *y1) { float t0, t1; int delta_x, delta_y; int visible = False; unsigned int x_right = screen_width; unsigned int x_left = 0; // Reverse the following two as our screen coordinate system is // upside down. The algorithm requires this order. unsigned int y_top = screen_height; unsigned int y_bottom = 0; if ( ( (*x0 < x_left ) && (*x1 < x_left ) ) || ( (*x0 > x_right ) && (*x1 > x_right ) ) || ( (*y0 < y_bottom) && (*y1 < y_bottom) ) || ( (*y0 > y_top ) && (*y1 > y_top ) ) ) { // Both endpoints are on same side of visible region and // outside of it, so reject. //fprintf(stderr,"reject 1\n"); return(visible); } t0 = 0; t1 = 1; delta_x = *x1 - *x0; if ( clipt_int(-delta_x, *x0 - x_left, &t0, &t1) ) // left { if ( clipt_int(delta_x, x_right - *x0, &t0, &t1) ) // right { delta_y = *y1 - *y0; if ( clipt_int(-delta_y, *y0 - y_bottom, &t0, &t1) ) // bottom { if ( clipt_int(delta_y, y_top - *y0, &t0, &t1) ) // top { // Compute coordinates if (t1 < 1) // Compute V1' (new x1,y1) { *x1 = *x0 + t1 * delta_x; *y1 = *y0 + t1 * delta_y; } if (t0 > 0) // Compute V0' (new x0,y0) { *x0 = *x0 + t0 * delta_x; *y0 = *y0 + t0 * delta_y; } visible = True; } else { //fprintf(stderr,"reject top\n"); } } else { //fprintf(stderr,"reject bottom\n"); } } else { //fprintf(stderr,"reject right\n"); } } else { //fprintf(stderr,"reject left\n"); } return(visible); } // Draws a point onto a pixmap. Assumes that the proper GC has // already been set up for drawing the correct color/width/linetype, // etc. If the bounding box containing the point doesn't intersect // with the current view, the point isn't drawn. // // Input point is in the Xastir coordinate system. // void draw_point(Widget w, unsigned long x1, unsigned long y1, GC gc, Pixmap which_pixmap, int skip_duplicates) { int x1i, y1i; static int last_x1i; static int last_y1i; // Check whether the two bounding boxes intersect. If not, skip // the rest of this function. Do we need to worry about // special-case code here to handle vertical/horizontal lines // (width or length of zero)? // // // WE7U // Check to see if we can do this faster than map_visible() can. A // point is a special case that should take only four compares // against the map window boundaries. // if (!map_visible(y1, y1, x1, x1)) { // Skip this vector //fprintf(stderr,"Point not visible\n"); return; } // Convert to screen coordinates. Careful here! // The format conversions you'll need if you try to // compress this into two lines will get you into // trouble. x1i = x1 - NW_corner_longitude; x1i = x1i / scale_x; y1i = y1 - NW_corner_latitude; y1i = y1i / scale_y; if (skip_duplicates) { if (x1i == last_x1i && y1i == last_y1i) { return; } } // XDrawPoint uses 16-bit unsigned integers // (shorts). Make sure we stay within the limits. (void)XDrawPoint(XtDisplay(w), which_pixmap, gc, l16(x1i), l16(y1i)); last_x1i = x1i; last_y1i = y1i; } // Draws a vector onto a pixmap. Assumes that the proper GC has // already been set up for drawing the correct color/width/linetype, // etc. // // Input points are in lat/long coordinates. // void draw_point_ll(Widget w, float y1, // lat1 float x1, // long1 GC gc, Pixmap which_pixmap, int skip_duplicates) { unsigned long x1L, y1L; // // WE7U // We should probably do four floating point compares against the // map boundaries here in order to speed up rejection of points that // don't fit our window. If we change to that, copy the conversion- // to-screen-coordinates and drawing stuff from draw_point() down to // this routine so that we don't go through the comparisons again // there. // // Convert the point to the Xastir coordinate system. convert_to_xastir_coordinates(&x1L, &y1L, x1, y1); // Call the draw routine above. draw_point(w, x1L, y1L, gc, which_pixmap, skip_duplicates); } // Draws a vector onto a pixmap. Assumes that the proper GC has // already been set up for drawing the correct color/width/linetype, // etc. If the bounding box containing the vector doesn't intersect // with the current view, the line isn't drawn. // // Input points are in the Xastir coordinate system. // void draw_vector(Widget w, unsigned long x1, unsigned long y1, unsigned long x2, unsigned long y2, GC gc, Pixmap which_pixmap, int skip_duplicates) { int x1i, x2i, y1i, y2i; static int last_x1i, last_x2i, last_y1i, last_y2i; //fprintf(stderr,"%ld,%ld %ld,%ld\t",x1,y1,x2,y2); if ( !clip2d_long(&x1, &y1, &x2, &y2) ) { // Skip this vector //fprintf(stderr,"Line not visible\n"); //fprintf(stderr,"%ld,%ld %ld,%ld\n",x1,y1,x2,y2); return; } //fprintf(stderr,"%ld,%ld %ld,%ld\n",x1,y1,x2,y2); // Convert to screen coordinates. Careful here! // The format conversions you'll need if you try to // compress this into two lines will get you into // trouble. x1i = x1 - NW_corner_longitude; x1i = x1i / scale_x; y1i = y1 - NW_corner_latitude; y1i = y1i / scale_y; x2i = x2 - NW_corner_longitude; x2i = x2i / scale_x; y2i = y2 - NW_corner_latitude; y2i = y2i / scale_y; if (skip_duplicates) { if (last_x1i == x1i && last_x2i == x2i && last_y1i == y1i && last_y2i == y2i) { return; } } // XDrawLine uses 16-bit unsigned integers // (shorts). Make sure we stay within the limits. // clip2d_long() should make sure of this anyway as it clips // lines to fit the window. // (void)XDrawLine(XtDisplay(w), which_pixmap, gc, l16(x1i), l16(y1i), l16(x2i), l16(y2i)); last_x1i = x1i; last_x2i = x2i; last_y1i = y1i; last_y2i = y2i; } // Draws a vector onto a pixmap. Assumes that the proper GC has // already been set up for drawing the correct color/width/linetype, // etc. // // Input points are in lat/long coordinates. // void draw_vector_ll(Widget w, float y1, // lat1 float x1, // long1 float y2, // lat2 float x2, // long2 GC gc, Pixmap which_pixmap, int skip_duplicates) { unsigned long x1L, x2L, y1L, y2L; int x1i, x2i, y1i, y2i; static int last_x1i, last_x2i, last_y1i, last_y2i; //fprintf(stderr,"%lf,%lf %lf,%lf\t",x1,y1,x2,y2); if ( !clip2d(&x1, &y1, &x2, &y2) ) { // Skip this vector //fprintf(stderr,"Line not visible: %lf,%lf %lf,%lf\n",y1,x1,y2,x2); return; } //fprintf(stderr,"%lf,%lf %lf,%lf\n",x1,y1,x2,y2); // Convert the points to the Xastir coordinate system. convert_to_xastir_coordinates(&x1L, &y1L, x1, y1); convert_to_xastir_coordinates(&x2L, &y2L, x2, y2); //fprintf(stderr,"%ld,%ld %ld,%ld\n",x1L,y1L,x2L,y2L); // Convert to screen coordinates. Careful here! // The format conversions you'll need if you try to // compress this into two lines will get you into // trouble. x1i = (int)(x1L - NW_corner_longitude); x1i = x1i / scale_x; y1i = (int)(y1L - NW_corner_latitude); y1i = y1i / scale_y; x2i = (int)(x2L - NW_corner_longitude); x2i = x2i / scale_x; y2i = (int)(y2L - NW_corner_latitude); y2i = y2i / scale_y; if (skip_duplicates) { if (last_x1i == x1i && last_x2i == x2i && last_y1i == y1i && last_y2i == y2i) { // fprintf(stderr,"Duplicate line\n"); return; } } //fprintf(stderr,"NW_corner_latitude:%ld, NW_corner_longitude:%ld, scale_x:%ld, scale_y:%ld\n", // NW_corner_latitude, // NW_corner_longitude, // scale_x, // scale_y); //fprintf(stderr,"%d,%d %d,%d\n\n",x1i,y1i,x2i,y2i); // XDrawLine uses 16-bit unsigned integers // (shorts). Make sure we stay within the limits. // clip2d() should make sure of this anyway as it clips lines to // fit the window. // (void)XDrawLine(XtDisplay(w), which_pixmap, gc, l16(x1i), l16(y1i), l16(x2i), l16(y2i)); last_x1i = x1i; last_x2i = x2i; last_y1i = y1i; last_y2i = y2i; } // Find length of a standard string of seven zeroes in the border font. // Supporting function for draw_grid() and the grid drawing functions. int get_standard_border_string_width_pixels(Widget w, int length) { int string_width_pixels = 0; // Width of the unrotated seven_zeroes label string in pixels. char seven_zeroes[8] = "0000000"; char five_zeroes[6] = "00000"; if (length==5) { string_width_pixels = get_rotated_label_text_length_pixels(w, five_zeroes, FONT_BORDER); } string_width_pixels = get_rotated_label_text_length_pixels(w, seven_zeroes, FONT_BORDER); return string_width_pixels; } // Find height of a standard string in the border font // Supporting function for draw_grid() and the grid drawing functions. int get_standard_border_string_height_pixels(Widget w) { int string_width_pixels = 0; // Width of the unrotated seven_zeroes label string in pixels. char one_zero[2] = "0"; string_width_pixels = get_rotated_label_text_height_pixels(w, one_zero, FONT_BORDER); return string_width_pixels; } // Find out the width to use for the border to apply when uing a labeled grid. // Border width is determined from the height of the border font. // Supporting function for draw_grid() and the grid drawing functions. int get_border_width(Widget w) { int border_width = 14; // The width of the border to draw around the // map to place labeled tick marks into // should be an even number. // The default here is overidden by size of the border font. int string_height_pixels = 0; // Height of a string in the border font in pixels string_height_pixels = get_standard_border_string_height_pixels(w); // Rotated text functions used to draw the border text add some // blank space at the bottom of the text so make the border wide enough // to compensate for this. border_width = string_height_pixels + (string_height_pixels/2) + 1; // check to see if string_height_pixels is even if ((float)string_height_pixels/2.0!=floor((float)string_height_pixels/2.0)) { border_width++; } // we are using draw nice string to write the metadata in the top border, so // make sure the border is wide enough for it, even if the grid labels are small. if (border_width < 14) { border_width = 14; } return border_width; } // *********************************************************** // // get_horizontal_datum() // // Provides the current map datum. Current default is WGS84. // Parameters: datum, character array ponter for the string // that will be filled with the current datum // sizeof_datum, the size of the datum character array. // //*********************************************************** void get_horizontal_datum(char *datum, int sizeof_datum) { char metadata_datum[6] = "WGS84"; // datum to display in metadata on top border xastir_snprintf(datum, sizeof_datum, "%s", metadata_datum); if (sizeof_datum<6) { fprintf(stderr,"Datum [%s] truncated to [%s]\n",metadata_datum,datum); } } // Lat/Long coordinate system, draw lat/long lines. Called by // draw_grid() below. // void draw_complete_lat_lon_grid(Widget w) { int coord; char dash[2]; unsigned int x,x1,x2; unsigned int y,y1,y2; unsigned int stepsx; // spacing of grid lines unsigned int stepsy; // spacing of grid lines int coordinate_format; // Format to use for coordinates on border (e.g. decimal degrees). char grid_label[25]; // String to draw labels on grid lines int screen_width_xastir; // screen width in xastir units (1/100 of a second) // int screen_height_xastir; // screen height in xastir units (1/100 of a second) int border_width; // the width of the labeled border in pixels. int string_width_pixels = 0;// Width of a grid label string in pixels. float screen_width_degrees; // Width of the screen in degrees int log_screen_width_degrees; // Log10 of the screen width in degrees, used to scale degrees long xx2, yy2; long xx, yy; unsigned int last_label_end; // cordinate of the end of the previous label char metadata_datum[6]; // datum to display in metadata on top border char grid_label1[25]; // String to draw latlong metadata char grid_label2[25]; // String to draw latlong metadata char top_label[180]; // String to draw metadata on top border if (!long_lat_grid) // We don't wish to draw a map grid { return; } // convert between selected coordinate format constant and display format constants if (coordinate_system == USE_DDDDDD) { coordinate_format = CONVERT_DEC_DEG; } else if (coordinate_system == USE_DDMMSS) { coordinate_format = CONVERT_DMS_NORMAL_FORMATED; } else { coordinate_format = CONVERT_HP_NORMAL_FORMATED; } border_width = get_border_width(w); // Find xastir coordinates of upper left and lower right corners. xx = NW_corner_longitude + (border_width * scale_x); yy = NW_corner_latitude + (border_width * scale_y); xx2 = NW_corner_longitude + ((screen_width - border_width) * scale_x); yy2 = NW_corner_latitude + ((screen_height - border_width) * scale_y); screen_width_xastir = xx2 - xx; // screen_height_xastir = yy2 - yy; // Determine some parameters used in drawing the border. string_width_pixels = get_standard_border_string_width_pixels(w, 7); // 1 xastir coordinate = 1/100 of a second // 100*60*60 xastir coordinates (=360000 xastir coordinates) = 1 degree // 64800000 xastir coordinates = 180 degrees // 360000 xastir coordinates = 1 degree // scale_x * (screen_width/10) = one tenth of the screen width in xastir coordinates // scale_x number of xastir coordinates per pixel screen_width_degrees = (float)(screen_width_xastir / (float)360000); log_screen_width_degrees = log10(screen_width_degrees); if (draw_labeled_grid_border==TRUE) { get_horizontal_datum(metadata_datum, sizeof(metadata_datum)); // Put metadata in top border. // find location of upper left corner of map, convert to Lat/Long convert_lon_l2s(xx, grid_label1, sizeof(grid_label1), coordinate_format); convert_lat_l2s(yy, grid_label2, sizeof(grid_label2), coordinate_format); strcpy(grid_label, grid_label1); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string strcat(grid_label, " "); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string strcat(grid_label, grid_label2); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string // find location of lower right corner of map, convert to Lat/Long convert_lon_l2s(xx2, grid_label1, sizeof(grid_label1), coordinate_format); convert_lat_l2s(yy2, grid_label2, sizeof(grid_label2), coordinate_format); //"XASTIR Map of %s (upper left) to %s %s (lower right). Lat/Long grid, %s datum. ", xastir_snprintf(top_label, sizeof(top_label), langcode("MDATA002"), grid_label,grid_label1,grid_label2,metadata_datum); draw_rotated_label_text_to_target (w, 270, border_width+2, border_width-1, sizeof(top_label),colors[0x10],top_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); } // A crude grid spacing can be obtained from scaling one tenth of the screen width. // This works, but puts the grid lines at arbitrary increments of a degree. //stepsx = (scale_x*(screen_width/10)); // Setting the grid using the base 10 log of the screen width in degrees allows // both scaling the grid to the screen and spacing the grid lines at appropriately // rounded increments of a degree. // // Set default grid to 0.1 degree. This will be used when the screen width is about 1 degree. stepsx = 36000; // if (log_screen_width_degrees == 0) // Work out an appropriate grid spacing for the screen size and coordinate system. if (log_screen_width_degrees > 0) { // grid spacing is rounded to 10 degree increment. stepsx = ((int)(screen_width_degrees / (pow(10,log_screen_width_degrees)))*pow(10,log_screen_width_degrees)) * 36000; } if (log_screen_width_degrees < 0) { // Grid spacing is rounded to less than one degree. if (coordinate_system == USE_DDDDDD) { // Round to tenths or hundrethds or thousanths of a degree. stepsx = ((float)(int)(((float)screen_width_degrees / pow(10,log_screen_width_degrees)*10.0)))*pow(10,log_screen_width_degrees) * 3600; } else { // For decimal minutes or minutes and seconds. // Find screen width and log screen width in minutes. screen_width_degrees = screen_width_degrees * 60.0; log_screen_width_degrees = log10(screen_width_degrees); // round to minutes or tenths of minutes. stepsx = ((float)(int) ((float)(screen_width_degrees) / pow(10,log_screen_width_degrees) * 10.0) ) * pow(10,log_screen_width_degrees) * 3600; if (log_screen_width_degrees==0) { stepsx = 600; // 6000 = 1 minute } } } // Grid should now be close to reasonable spacing for screen size, but // may need to be tuned. // Test for too tightly or too coarsely spaced grid. if (stepsx<(unsigned int)(scale_x * string_width_pixels * 1.5)) { stepsx = stepsx * 2.0; } if (stepsx<(unsigned int)(scale_x * string_width_pixels * 1.5)) { stepsx = stepsx * 2.0; } if (stepsx>(unsigned int)((scale_x * screen_width)/3.5)) { stepsx = stepsx / 2.0; } // Handle special case of very small screen - only draw a single grid line if (screen_width < (string_width_pixels * 2)) { stepsx = (scale_x*(screen_width/2)); } // Make sure we don't pass an erronous stepsx of 0 on. if (stepsx==0) { stepsx=36000; } // Use the same grid spacing for both latitude and longitude grids. // We could use a scaling factor related to the screen height width ratio here. stepsy = stepsx; // protect against division by zero if (scale_x==0) { scale_x = 1; } if (scale_y==0) { scale_y = 1; } // Now draw and label the grid. // Draw vertical longitude lines if (NW_corner_latitude >= 0) { y1 = 0; } else { y1 = -NW_corner_latitude/scale_y; } y2 = (180*60*60*100-NW_corner_latitude)/scale_y; if (y2 > (unsigned int)screen_height) { y2 = screen_height-1; } coord = NW_corner_longitude+stepsx-(NW_corner_longitude%stepsx); if (coord < 0) { coord = 0; } last_label_end = 0; for (; coord < SE_corner_longitude && coord <= 360*60*60*100; coord += stepsx) { x = (coord-NW_corner_longitude)/scale_x; if ((coord%(648000*100)) == 0) { (void)XSetLineAttributes (XtDisplay (w), gc_tint, 1, LineSolid, CapButt, JoinMiter); (void)XDrawLine (XtDisplay (w), pixmap_final, gc_tint, l16(x), l16(y1), l16(x), l16(y2)); (void)XSetLineAttributes (XtDisplay (w), gc_tint, 1, LineOnOffDash, CapButt, JoinMiter); continue; // Go to next iteration of for loop } else if ((coord%(72000*100)) == 0) { dash[0] = dash[1] = 8; (void)XSetDashes (XtDisplay (w), gc_tint, 0, dash, 2); } else if ((coord%(7200*100)) == 0) { dash[0] = dash[1] = 4; (void)XSetDashes (XtDisplay (w), gc_tint, 0, dash, 2); } else if ((coord%(300*100)) == 0) { dash[0] = dash[1] = 2; (void)XSetDashes (XtDisplay (w), gc_tint, 0, dash, 2); } (void)XDrawLine (XtDisplay (w), pixmap_final, gc_tint, l16(x), l16(y1), l16(x), l16(y2)); if (draw_labeled_grid_border==TRUE) { // Label the longitudes in lower border. convert_lon_l2s(coord, grid_label, sizeof(grid_label), coordinate_format); if (log_screen_width_degrees > 0 && strlen(grid_label) > 5) { // truncate the grid_label string if (coordinate_system==USE_DDMMMM) { // Add ' and move EW and null characters forward. grid_label[strlen(grid_label)-5] = '\''; grid_label[strlen(grid_label)-4] = grid_label[strlen(grid_label)-1]; grid_label[strlen(grid_label)-3] = grid_label[strlen(grid_label)]; } else { // Move EW and null characters forward. grid_label[strlen(grid_label)-5] = grid_label[strlen(grid_label)-1]; grid_label[strlen(grid_label)-4] = grid_label[strlen(grid_label)]; } } string_width_pixels = get_rotated_label_text_length_pixels(w, grid_label, FONT_BORDER); // test for overlap of label with previously printed label if (x > last_label_end + 3 && x < (unsigned int)(screen_width - string_width_pixels)) { draw_rotated_label_text_to_target (w, 270, x, screen_height, sizeof(grid_label),colors[0x09],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); last_label_end = x + string_width_pixels; } } } // Draw horizontal latitude lines. last_label_end = 0; if (NW_corner_longitude >= 0) { x1 = 0; } else { x1 = -NW_corner_longitude/scale_x; } x2 = (360*60*60*100-NW_corner_longitude)/scale_x; if (x2 > (unsigned int)screen_width) { x2 = screen_width-1; } coord = NW_corner_latitude+stepsy-(NW_corner_latitude%stepsy); if (coord < 0) { coord = 0; } for (; coord < SE_corner_latitude && coord <= 180*60*60*100; coord += stepsy) { y = (coord-NW_corner_latitude)/scale_y; if ((coord%(324000*100)) == 0) { (void)XSetLineAttributes (XtDisplay (w), gc_tint, 1, LineSolid, CapButt, JoinMiter); (void)XDrawLine (XtDisplay (w), pixmap_final, gc_tint, l16(x1), l16(y), l16(x2), l16(y)); (void)XSetLineAttributes (XtDisplay (w), gc_tint, 1, LineOnOffDash, CapButt, JoinMiter); continue; // Go to next iteration of for loop } else if ((coord%(36000*100)) == 0) { dash[0] = dash[1] = 8; (void)XSetDashes (XtDisplay (w), gc_tint, 4, dash, 2); } else if ((coord%(3600*100)) == 0) { dash[0] = dash[1] = 4; (void)XSetDashes (XtDisplay (w), gc_tint, 2, dash, 2); } else if ((coord%(150*100)) == 0) { dash[0] = dash[1] = 2; (void)XSetDashes (XtDisplay (w), gc_tint, 1, dash, 2); } (void)XDrawLine (XtDisplay (w), pixmap_final, gc_tint, l16(x1), l16(y), l16(x2), l16(y)); if (draw_labeled_grid_border==TRUE) { // label the latitudes on left and right borders // (unlike UTM where easting before northing order is important) convert_lat_l2s(coord, grid_label, sizeof(grid_label), coordinate_format); if (log_screen_width_degrees > 0 && strlen(grid_label) > 5) { // truncate the grid_label string if (coordinate_system==USE_DDMMMM) { // Add ' and move EW and null characters forward. grid_label[strlen(grid_label)-5] = '\''; grid_label[strlen(grid_label)-4] = grid_label[strlen(grid_label)-1]; grid_label[strlen(grid_label)-3] = grid_label[strlen(grid_label)]; } else { // Move EW and null characters forward. grid_label[strlen(grid_label)-5] = grid_label[strlen(grid_label)-1]; grid_label[strlen(grid_label)-4] = grid_label[strlen(grid_label)]; } } string_width_pixels = get_rotated_label_text_length_pixels(w, grid_label, FONT_BORDER); // check to make sure we aren't overwriting the previous label text if ((y > last_label_end+3) && (y > (unsigned int)string_width_pixels)) { draw_rotated_label_text_to_target (w, 180, screen_width, y, sizeof(grid_label),colors[0x09],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); draw_rotated_label_text_to_target (w, 180, border_width, y, sizeof(grid_label),colors[0x09],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); last_label_end = y + string_width_pixels; } } } } // End of draw_complete_lat_lon_grid() // Draw the major zones for UTM and MGRS. Called by draw_grid() // below. // // These are based off 6-degree lat/long lines, with a few irregular // zones that have to be special-cased. This part of the code // handles the irregular zones in SW Norway (31V/32V) and the // regions near Svalbard (31X/33X/35X/37X) just fine. // These are based off the central meridian running up the middle of // each zone (3 degrees from either side of the standard six-degree // zones). Even the irregular zones key off the same medians. UTM // grids are defined in terms of meters instead of lat/long, so they // don't line up with the left/right edges of the zones or with the // longitude lines. // // According to Peter Dana (Geographer's Craft web pages), even when // the major grid boundaries have been shifted, the meridian used // for drawing the subgrids is still based on six-degree boundaries // (as if the major grid hadn't been shifted at all). That means we // are drawing the subgrids correctly as it stands now for the // irregular grids (31V/32V/31X/33X/35X/37X). The irregular zones // have sizes of 3/9/12 degrees (width) instead of 6 degrees. // // UTM NOTES: 84 degrees North to 80 degrees South. 60 zones, each // covering six (6) degrees of longitude. Each zone extends three // degrees eastward and three degrees westward from its central // meridian. Zones are numbered consecutively west to east from the // 180 degree meridian. From 84 degrees North and 80 degrees South // to the respective poles, the Universal Polar Stereographic (UPS) // is used. // // For MGRS and UTM-Special grid only: // UTM Zone 32 has been widened to 9° (at the expense of zone 31) // between latitudes 56° and 64° (band V) to accommodate southwest // Norway. Thus zone 32 extends westwards to 3°E in the North Sea. // Similarly, between 72° and 84° (band X), zones 33 and 35 have // been widened to 12° to accommodate Svalbard. To compensate for // these 12° wide zones, zones 31 and 37 are widened to 9° and zones // 32, 34, and 36 are eliminated. Thus the W and E boundaries of // zones are 31: 0 - 9 E, 33: 9 - 21 E, 35: 21 - 33 E and 37: 33 - // 42 E. // // UTM is depending on the ellipsoid and the datum used. For our // purposes, we're always using WGS84 ellipsoid and datum, so it's a // non-issue. // // Horizontal bands corresponding to the NATO UTM/UPS lettering: // Zones go from A (south pole) to Z (north pole). South of -80 are // zones A/B, north of +84 are zones Y/Z. "I" and "O" are not used. // Zones from C to W are 8 degrees high. Zone X is 12 degrees high. // // We need these NATO letters or a N/S designator in order to // specify which hemisphere the UTM coordinates are in. Often, the // same coordinates can appear in either hemisphere. Some computer // software uses +/- to designate northings instead of N/S or the // NATO lettered bands. // // UPS system is used at the poles instead of UTM. UPS uses a false // northing and easting of 2,000,000 meters. // // An arbitrary false northing of 10,000,000 at the equator is used // for southern latitudes only. Northern latitudes assume the // equator northing is at zero. An arbitrary false easting of // 500,000 is along the meridian of each zone (3 degrees from each // side). The lettered grid lines are necessary due to some // coordinates being valid in both the northern and the southern // hemisphere. // // Y/Z 84N to 90N (UPS System) false N/E = 2,000,000 // X 72N to 84N (12 degrees latitude, equator=0) // W 64N to 72N ( 8 degrees latitude, equator=0) // V 56N to 64N ( 8 degrees latitude, equator=0) // U 48N to 56N ( 8 degrees latitude, equator=0) // T 40N to 48N ( 8 degrees latitude, equator=0) // S 32N to 40N ( 8 degrees latitude, equator=0) // R 24N to 32N ( 8 degrees latitude, equator=0) // Q 16N to 24N ( 8 degrees latitude, equator=0) // P 8N to 16N ( 8 degrees latitude, equator=0) // N 0N to 8N ( 8 degrees latitude, equator=0) // M 0S to 8S ( 8 degrees latitude, equator=10,000,000) // L 8S to 16S ( 8 degrees latitude, equator=10,000,000) // K 16S to 24S ( 8 degrees latitude, equator=10,000,000) // J 24S to 32S ( 8 degrees latitude, equator=10,000,000) // H 32S to 40S ( 8 degrees latitude, equator=10,000,000) // G 40S to 48S ( 8 degrees latitude, equator=10,000,000) // F 48S to 56S ( 8 degrees latitude, equator=10,000,000) // E 56S to 64S ( 8 degrees latitude, equator=10,000,000) // D 64S to 72S ( 8 degrees latitude, equator=10,000,000) // C 72S to 80S ( 8 degrees latitude, equator=10,000,000) // A/B 80S to 90S (UPS System) false N/E = 2,000,000 // void draw_major_utm_mgrs_grid(Widget w) { int ii; // need to move metadata to its own function and put it up after grids have been drawn. //******* // variables for metadata in grid border long xx2, yy2; // coordinates of screen corners used for metadata int border_width; // Width of the border to draw labels into. double easting, northing; // Values used in border metadata. int x, y; // Screen coordinates for border labels. char zone_str[10]; char zone_str2[10]; char metadata_datum[6]; char grid_label[25]; // String to draw labels on grid lines char grid_label1[25]; // String to draw latlong metadata char top_label[180]; // String to draw metadata on top border // variables to support components of MGRS strings in the metadata char mgrs_zone[4] = " "; // MGRS zone letter char mgrs_eastingL[3] = " "; char mgrs_northingL[3] = " "; unsigned int int_utmEasting; unsigned int int_utmNorthing; char mgrs_space_string[4] = " "; // int mgrs_single_digraph = FALSE; // mgrs_ul_digraph and mgrs_ur_digraph are the same. char mgrs_ul_digraph[3] = " "; // MGRS digraph for upper left corner of screen char mgrs_lr_digraph[3] = " "; // MGRS digraph for lower right corner of screen //fprintf(stderr,"draw_major_utm_mgrs_grid start\n"); if (!long_lat_grid) // We don't wish to draw a map grid { return; } // Vertical lines: // Draw the vertical vectors (except for the irregular regions // and the prime meridian). The polar areas only have two zones // each, so we don't want to draw through those areas. for (ii = -180; ii < 0; ii += 6) { draw_vector_ll(w, -80.0, (float)ii, 84.0, (float)ii, gc_tint, pixmap_final, 0); } for (ii = 42; ii <= 180; ii += 6) { draw_vector_ll(w, -80.0, (float)ii, 84.0, (float)ii, gc_tint, pixmap_final, 0); } // Draw the short vertical vectors in the polar regions draw_vector_ll(w, -90.0, -180.0, -80.0, -180.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, -90.0, 180.0, -80.0, 180.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, -180.0, 90.0, -180.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, 180.0, 90.0, 180.0, gc_tint, pixmap_final, 0); if (coordinate_system == USE_UTM_SPECIAL || coordinate_system == USE_MGRS) { // For MGRS, we need to draw irregular zones in certain // areas. // Draw the partial vectors from 80S to the irregular region draw_vector_ll(w, -80.0, 6.0, 56.0, 6.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, -80.0, 12.0, 72.0, 12.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, -80.0, 18.0, 72.0, 18.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, -80.0, 24.0, 72.0, 24.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, -80.0, 30.0, 72.0, 30.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, -80.0, 36.0, 72.0, 36.0, gc_tint, pixmap_final, 0); // Draw the short vertical vectors in the irregular region draw_vector_ll(w, 56.0, 3.0, 64.0, 3.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 64.0, 6.0, 72.0, 6.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 72.0, 9.0, 84.0, 9.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 72.0, 21.0, 84.0, 21.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 72.0, 33.0, 84.0, 33.0, gc_tint, pixmap_final, 0); // Draw the short vertical vectors above the irregular region draw_vector_ll(w, 84.0, 6.0, 84.0, 6.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, 12.0, 84.0, 12.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, 18.0, 84.0, 18.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, 24.0, 84.0, 24.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, 30.0, 84.0, 30.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 84.0, 36.0, 84.0, 36.0, gc_tint, pixmap_final, 0); } else { // Draw normal zone boundaries used for civilian UTM // grid. for (ii = 6; ii < 42; ii += 6) { draw_vector_ll(w, -80.0, (float)ii, 84.0, (float)ii, gc_tint, pixmap_final, 0); } } // Horizontal lines: // Draw the 8 degree spaced lines, except for the equator for (ii = -80; ii < 0; ii += 8) { draw_vector_ll(w, (float)ii, -180.0, (float)ii, 180.0, gc_tint, pixmap_final, 0); } // Draw the 8 degree spaced lines for (ii = 8; ii <= 72; ii += 8) { draw_vector_ll(w, (float)ii, -180.0, (float)ii, 180.0, gc_tint, pixmap_final, 0); } // Draw the one 12 degree spaced line draw_vector_ll(w, 84.0, -180.0, 84.0, 180.0, gc_tint, pixmap_final, 0); // Draw the pole lines draw_vector_ll(w, -90.0, -180.0, -90.0, 180.0, gc_tint, pixmap_final, 0); draw_vector_ll(w, 90.0, -180.0, 90.0, 180.0, gc_tint, pixmap_final, 0); // Set to solid line for the equator. Make it extra wide as // well. (void)XSetLineAttributes (XtDisplay (w), gc_tint, 3, LineSolid, CapButt,JoinMiter); // Draw the equator as a solid line draw_vector_ll(w, 0.0, -180.0, 0.0, 180.0, gc_tint, pixmap_final, 0); (void)XSetLineAttributes (XtDisplay (w), gc_tint, 2, LineSolid, CapButt,JoinMiter); // Draw the prime meridian in the same manner draw_vector_ll(w, -80.0, 0.0, 84.0, 0.0, gc_tint, pixmap_final, 0); // add metadata and labels if (draw_labeled_grid_border==TRUE && scale_x > 3000) { // Determine the width of the border border_width = get_border_width(w); // Find out what the map datum is. get_horizontal_datum(metadata_datum, sizeof(metadata_datum)); // Put metadata in top border. // find location of upper left corner of map, convert to UTM xx2 = NW_corner_longitude + (border_width * scale_x); yy2 = NW_corner_latitude + (border_width * scale_y); convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx2, yy2); if (coordinate_system == USE_MGRS) { convert_xastir_to_MGRS_str_components(mgrs_zone, strlen(mgrs_zone), mgrs_eastingL, sizeof(mgrs_eastingL), mgrs_northingL, sizeof(mgrs_northingL), &int_utmEasting, &int_utmNorthing, xx2, yy2, 0, mgrs_space_string, strlen(mgrs_space_string)); xastir_snprintf(mgrs_ul_digraph, sizeof(mgrs_ul_digraph), "%c%c", mgrs_eastingL[0], mgrs_northingL[0]); xastir_snprintf(grid_label, sizeof(grid_label), "%s %s %05.0f %05.0f", mgrs_zone,mgrs_ul_digraph,(float)int_utmEasting,(float)int_utmNorthing); } else { char easting_str[10]; char northing_str[10]; xastir_snprintf(easting_str, sizeof(easting_str), " %07.0f", easting); xastir_snprintf(northing_str, sizeof(northing_str), " %07.0f", northing); strcpy(grid_label, zone_str); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string strcat(grid_label, easting_str); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string strcat(grid_label, northing_str); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string } // find location of lower right corner of map, convert to UTM xx2 = NW_corner_longitude + ((screen_width - border_width) * scale_x); yy2 = NW_corner_latitude + ((screen_height - border_width) * scale_y); convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx2, yy2); if (coordinate_system == USE_MGRS) { convert_xastir_to_MGRS_str_components(mgrs_zone, strlen(mgrs_zone), mgrs_eastingL, sizeof(mgrs_eastingL), mgrs_northingL, sizeof(mgrs_northingL), &int_utmEasting, &int_utmNorthing, xx2, yy2, 0, mgrs_space_string, strlen(mgrs_space_string)); xastir_snprintf(mgrs_lr_digraph, sizeof(mgrs_lr_digraph), "%c%c", mgrs_eastingL[0], mgrs_northingL[0]); xastir_snprintf(grid_label1, sizeof(grid_label1), "%s %s %05.0f %05.0f", mgrs_zone,mgrs_lr_digraph,(float)int_utmEasting,(float)int_utmNorthing); if (strcmp(mgrs_lr_digraph,mgrs_ul_digraph)==0) { // mgrs_single_digraph = TRUE; // mgrs_ul_digraph and mgrs_ur_digraph are the same. } else { // mgrs_single_digraph = FALSE; // mgrs_ul_digraph and mgrs_ur_digraph are the same. } } else { char easting_str[10]; char northing_str[10]; xastir_snprintf(easting_str, sizeof(easting_str), " %07.0f", easting); xastir_snprintf(northing_str, sizeof(northing_str), " %07.0f", northing); strcpy(grid_label1, zone_str); grid_label1[sizeof(grid_label1)-1] = '\0'; // Terminate string strcat(grid_label1, easting_str); grid_label1[sizeof(grid_label1)-1] = '\0'; // Terminate string strcat(grid_label1, northing_str); grid_label1[sizeof(grid_label1)-1] = '\0'; // Terminate string } // Write metadata on upper border of map. //"XASTIR Map of %s (upper left) to %s (lower right). UTM zones, %s datum. ", xastir_snprintf(top_label, sizeof(top_label), langcode("MDATA003"), grid_label,grid_label1,metadata_datum); draw_rotated_label_text_to_target (w, 270, border_width+2, border_width-1, sizeof(top_label),colors[0x10],top_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); // Crudely identify zone boundaries by // iterating across bottom border. xastir_snprintf(zone_str2, sizeof(zone_str2), "%s"," "); for (x=1; x<(screen_width - border_width); x++) { xx2 = NW_corner_longitude + (x * scale_x); yy2 = NW_corner_latitude + ((screen_height - border_width) * scale_y); convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx2, yy2); zone_str[strlen(zone_str)-1] = '\0'; if (strcmp(zone_str,zone_str2) !=0) { draw_rotated_label_text_to_target (w, 270, x + 1, screen_height, sizeof(zone_str),colors[0x10],zone_str,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); } xastir_snprintf(zone_str2, sizeof(zone_str2), "%s",zone_str); } // Crudely identify zone letters by iterating down left border for (y=(border_width*2); y<(screen_height - border_width); y++) { xx2 = NW_corner_longitude + (border_width * scale_x); yy2 = NW_corner_latitude + (y * scale_y); convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx2, yy2); zone_str[0] = zone_str[strlen(zone_str)-1]; zone_str[1] = '\0'; if (strcmp(zone_str,zone_str2) !=0) { draw_rotated_label_text_to_target (w, 270, 1, y, sizeof(zone_str),colors[0x10],zone_str,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); } xastir_snprintf(zone_str2, sizeof(zone_str2), "%s",zone_str); } } // end if draw labeled border // Set the line width and style in the GC to 1 pixel wide for // drawing the smaller grid (void)XSetLineAttributes (XtDisplay (w), gc_tint, 1, LineOnOffDash, CapButt,JoinMiter); //fprintf(stderr,"draw_major_utm_mgrs_grid end\n"); } // End of draw_major_utm_mgrs_grid() // This is the function which actually draws a minor UTM grid. // Called by draw_minor_utm_mgrs_grid() function below. // draw_minor_utm_mgrs_grid() is the function which calculates the // grid points. // void actually_draw_utm_minor_grid(Widget w) { int border_width; // Width of the border to draw labels into. int numberofzones = 0; // number of elements in utm_grid.zone[] that are used int Zone; int ii; int easting_color; // Colors for the grid labels int northing_color; int zone_color; // zone label color int label_on_left; // if true, draw northing labels on left long xx, yy, xx2, yy2; char zone_str[10]; char zone_str2[10]; double easting, northing; int short_width_pixels = 0; // Width of an unrotated string of five_zeroes in the border font in pixels. int string_width_pixels = 0;// Width of an unrotated string of seven_zeroes in the border font in pixels. char metadata_datum[6]; char grid_label[25]; // String to draw labels on grid lines char grid_label1[25]; // String to draw latlong metadata char top_label[180]; // String to draw metadata on top border int grid_spacing_pixels; // Spacing of fine grid lines in pixels. int bottom_point; // utm_grid.zone[].col[].npoints can extend past the // bottom of the screen, this is the lowest point in the // points array that is on the screen. int skip_alternate_label; // Skip alternate easting and northing labels // if they would overlap on the // display. int last_line_labeled; // Marks lines that were labeled // when alternate lines are not // being labeled. // variables to support components of MGRS strings char mgrs_zone[4] = " "; // MGRS zone letter char mgrs_eastingL[3] = " "; char mgrs_northingL[3] = " "; unsigned int int_utmEasting; unsigned int int_utmNorthing; char mgrs_space_string[4] = " "; int mgrs_single_digraph = FALSE; // mgrs_ul_digraph and mgrs_ur_digraph are the same. char mgrs_ul_digraph[3] = " "; // MGRS digraph for upper left corner of screen char mgrs_lr_digraph[3] = " "; // MGRS digraph for lower right corner of screen if (!long_lat_grid) // We don't wish to draw a map grid { return; } // OLD: Draw grid in dashed white lines. // NEW: Tint the lines as they go along, making them appear // no matter what color is underneath. (void)XSetForeground(XtDisplay(w), gc_tint, colors[0x27]); // Note: npoints can be negative here! Make sure our code // checks for that. Initially npoints was an unsigned int. // Changed it to an int so that we can get and check for // negative values, bypassing segfaults. // numberofzones = 0; // Determine the width of the border border_width = get_border_width(w); // Determine some parameters used in drawing the border. string_width_pixels = get_standard_border_string_width_pixels(w, 7); short_width_pixels = get_standard_border_string_width_pixels(w,5); for (Zone=0; Zone < UTM_GRID_MAX_ZONES; Zone++) { if (utm_grid.zone[Zone].ncols > 0) { // find out how many zones are actually drawn on the map numberofzones++; } } for (Zone=0; Zone < UTM_GRID_MAX_ZONES; Zone++) { for (ii=0; ii < (int)utm_grid.zone[Zone].ncols; ii++) { if (utm_grid.zone[Zone].col[ii].npoints > 1) { // We need to check for points that are more // than +/- 16383. If we have any, it can cause // X11 to lock up for a while drawing lots of // extra lines, due to bugs in X11. We do that // checking above with xx and yy. // (void)XDrawLines(XtDisplay(w), pixmap_final, gc_tint, utm_grid.zone[Zone].col[ii].points, l16(utm_grid.zone[Zone].col[ii].npoints), CoordModeOrigin); } } for (ii=0; ii < (int)utm_grid.zone[Zone].nrows; ii++) { if (utm_grid.zone[Zone].row[ii].npoints > 1) { // We need to check for points that are more // than +/- 16383. If we have any, it can cause // X11 to lock up for a while drawing lots of // extra lines, due to bugs in X11. We do that // checking above with xx and yy. // (void)XDrawLines(XtDisplay(w), pixmap_final, gc_tint, utm_grid.zone[Zone].row[ii].points, l16(utm_grid.zone[Zone].row[ii].npoints), CoordModeOrigin); } } // Check each of the 4 possible utm_grid.zone array elements // that might contain a grid, and label the grid if it exists. if (utm_grid.zone[Zone].nrows>0 && utm_grid.zone[Zone].ncols>0) { if (draw_labeled_grid_border==TRUE) { // Label the UTM grid on the border. // Since the coordinate of the current mouse pointer position is // continually updated, labeling the grid is primarily for the // purpose of printing maps and saving screenshots. // // ******* Doesn't work properly near poles when 3 zones are on screen // ******* (e.g. 13,14,15) - overlaps northings for 14 and 15. // ******* Doesn't clearly distinguish one zone with 2 lettered rows // ******* (e.g. 18T,18U) needs color distinction between northings // ******* to indicate which northings are in which lettered row. // // Default labels for just one zone on screen are black text for // zone at lower left corner, eastings on bottom, and northings // at right. // Idea is to normally start at the lower left corner // users can then easily follow left to right to get easting, // and bottom to top to get northing. // For two zones, second zone uses blue text for eastings and northings. easting_color = 0x08; // black text northing_color = 0x08; // black text zone_color = 0x08; // black text // 0x09=blue (0x0e=yellow works well with outline, but not without). label_on_left = FALSE; // Find out what the map datum is. get_horizontal_datum(metadata_datum, sizeof(metadata_datum)); if (numberofzones>1) { // check to see if the upper left and lower left corners are in the same zone // if not, label the upper left corner xx = (border_width * scale_x) + NW_corner_longitude; yy = ((screen_height - border_width) * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx, yy); yy = (border_width * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str2, sizeof(zone_str2), xx, yy); if (strcmp(zone_str,zone_str2)!=0) { xastir_snprintf(grid_label, sizeof(grid_label), "%s", zone_str2); //draw_nice_string(w,pixmap_final,0, // border_width+2, // (2*border_width)+2, // grid_label, // 0x10,zone_color,(int)strlen(grid_label)); draw_rotated_label_text_to_target (w, 270, border_width+2, (2*border_width)+2, sizeof(grid_label),colors[zone_color],grid_label,FONT_BORDER, pixmap_final, 1, colors[0x0f]); } if (strcmp(zone_str,zone_str2)!=0) { xastir_snprintf(grid_label, sizeof(grid_label), "%s", zone_str); draw_rotated_label_text_to_target (w, 270, border_width+2, screen_height - (2*border_width) - 2, sizeof(grid_label),colors[zone_color],grid_label,FONT_BORDER, pixmap_final, 1, colors[0x0f]); } zone_color = 0x09; // likewise for upper and lower right corners xx = ((screen_width - border_width) * scale_x) + NW_corner_longitude; yy = ((screen_height - border_width) * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx, yy); yy = (border_width * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str2, sizeof(zone_str2), xx, yy); if (strcmp(zone_str,zone_str2)!=0) { xastir_snprintf(grid_label, sizeof(grid_label), "%s", zone_str2); //draw_nice_string(w,pixmap_final,0, // screen_width - (border_width * 3) , // (2*border_width)+2, // grid_label, // 0x10,zone_color,(int)strlen(grid_label)); draw_rotated_label_text_to_target (w, 270, screen_width - (border_width * 3), (2*border_width)+2, sizeof(grid_label),colors[zone_color],grid_label,FONT_BORDER, pixmap_final, 1, colors[0x0f]); } if (strcmp(zone_str,zone_str2)!=0) { xastir_snprintf(grid_label, sizeof(grid_label), "%s", zone_str); draw_rotated_label_text_to_target (w, 270, screen_width - (border_width * 3), screen_height - (2*border_width) - 2, sizeof(grid_label),colors[zone_color],grid_label,FONT_BORDER, pixmap_final, 1, colors[0x0f]); } // are we currently the same zone as the upper left corner // if so, we need to place the northing labels on the left side xx = (utm_grid.zone[Zone].col[0].points[0].x * scale_x) + NW_corner_longitude; yy = (utm_grid.zone[Zone].col[0].points[0].y * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx, yy); convert_xastir_to_UTM(&easting, &northing, zone_str2, sizeof(zone_str2), NW_corner_longitude, NW_corner_latitude); if (strcmp(zone_str,zone_str2)==0) { northing_color = 0x08; // 0x08 = black, same as lower left easting label_on_left = TRUE; } } // check to see if there is a horizontal boundary // compare xone of upper left and lower left corners convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), NW_corner_longitude, NW_corner_latitude); // Overwrite defaults as appropriate and // label zones differently if more than one appears on the screen. if (Zone > 0) { // write the zone label on the bottom border zone_color = 0x09; // blue easting_color = 0x09; // blue northing_color = 0x09; // blue xx2 = utm_grid.zone[Zone].col[0].points[0].x; xx = (xx2 * scale_x) + NW_corner_longitude; yy2 = utm_grid.zone[Zone].col[0].points[utm_grid.zone[Zone].col[0].npoints-1].y; yy = (yy2 * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx,yy); xastir_snprintf(grid_label, sizeof(grid_label), "%s", zone_str); draw_rotated_label_text_to_target (w, 270, xx2, screen_height, sizeof(grid_label),colors[easting_color],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); //draw_nice_string(w,pixmap_final,0, // xx2, // screen_height - 2, // grid_label, // 0x10,zone_color,(int)strlen(grid_label)); } if (Zone==0) { // write the zone of the lower left corner of the map xx = (border_width * scale_x) + NW_corner_longitude; yy = ((screen_height - border_width) * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx, yy); xastir_snprintf(grid_label, sizeof(grid_label), "%s", zone_str); draw_rotated_label_text_to_target (w, 270, 1, screen_height, sizeof(grid_label),colors[easting_color],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); //draw_nice_string(w,pixmap_final,0, // 1, // screen_height - 2, // grid_label, // 0x10,0x20,(int)strlen(grid_label)); } // Put metadata in top border. // find location of upper left corner of map, convert to UTM xx2 = NW_corner_longitude + (border_width * scale_x); yy2 = NW_corner_latitude + (border_width * scale_y); convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx2, yy2); if (coordinate_system == USE_MGRS) { convert_xastir_to_MGRS_str_components(mgrs_zone, strlen(mgrs_zone), mgrs_eastingL, sizeof(mgrs_eastingL), mgrs_northingL, sizeof(mgrs_northingL), &int_utmEasting, &int_utmNorthing, xx2, yy2, 0, mgrs_space_string, strlen(mgrs_space_string)); xastir_snprintf(mgrs_ul_digraph, sizeof(mgrs_ul_digraph), "%c%c", mgrs_eastingL[0], mgrs_northingL[0]); xastir_snprintf(grid_label, sizeof(grid_label), "%s %s %05.0f %05.0f", mgrs_zone,mgrs_ul_digraph,(float)int_utmEasting,(float)int_utmNorthing); } else { char easting_str[10]; char northing_str[10]; xastir_snprintf(easting_str, sizeof(easting_str), " %07.0f", easting); xastir_snprintf(northing_str, sizeof(northing_str), " %07.0f", northing); strcpy(grid_label, zone_str); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string strcat(grid_label, easting_str); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string strcat(grid_label, northing_str); grid_label[sizeof(grid_label)-1] = '\0'; // Terminate string } // find location of lower right corner of map, convert to UTM xx2 = NW_corner_longitude + ((screen_width - border_width) * scale_x); yy2 = NW_corner_latitude + ((screen_height - border_width) * scale_y); convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx2, yy2); if (coordinate_system == USE_MGRS) { convert_xastir_to_MGRS_str_components(mgrs_zone, strlen(mgrs_zone), mgrs_eastingL, sizeof(mgrs_eastingL), mgrs_northingL, sizeof(mgrs_northingL), &int_utmEasting, &int_utmNorthing, xx2, yy2, 0, mgrs_space_string, strlen(mgrs_space_string)); xastir_snprintf(mgrs_lr_digraph, sizeof(mgrs_lr_digraph), "%c%c", mgrs_eastingL[0], mgrs_northingL[0]); xastir_snprintf(grid_label1, sizeof(grid_label1), "%s %s %05.0f %05.0f", mgrs_zone,mgrs_lr_digraph,(float)int_utmEasting,(float)int_utmNorthing); if (strcmp(mgrs_lr_digraph,mgrs_ul_digraph)==0) { mgrs_single_digraph = TRUE; // mgrs_ul_digraph and mgrs_ur_digraph are the same. } else { mgrs_single_digraph = FALSE; // mgrs_ul_digraph and mgrs_ur_digraph are the same. } } else { char easting_str[10]; char northing_str[10]; xastir_snprintf(easting_str, sizeof(easting_str), " %07.0f", easting); xastir_snprintf(northing_str, sizeof(northing_str), " %07.0f", northing); strcpy(grid_label1, zone_str); grid_label1[sizeof(grid_label1)-1] = '\0'; // Terminate string strcat(grid_label1, easting_str); grid_label1[sizeof(grid_label1)-1] = '\0'; // Terminate string strcat(grid_label1, northing_str); grid_label1[sizeof(grid_label1)-1] = '\0'; // Terminate string } //"XASTIR Map of %s (upper left) to %s (lower right). UTM %d m grid, %s datum. ", xastir_snprintf(top_label, sizeof(top_label), langcode("MDATA001"), grid_label,grid_label1,utm_grid_spacing_m,metadata_datum); //draw_nice_string(w,pixmap_final,0, // border_width+2, // border_width-2, // top_label, // 0x10,0x20,(int)strlen(top_label)); draw_rotated_label_text_to_target (w, 270, border_width+2, border_width-1, sizeof(top_label),colors[0x10],top_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); // deterimne whether the easting and northing strings will fit // in a grid box, or whether easting strings in adjacent boxes // will overlap (so that alternate strings can be skipped). if (utm_grid.zone[Zone].ncols > 1) { // find out the number of pixels beteen two grid lines grid_spacing_pixels = utm_grid.zone[Zone].col[1].points[0].x - utm_grid.zone[Zone].col[0].points[0].x; if (grid_spacing_pixels == 0) { grid_spacing_pixels = -1; // Skip } } else { // only one column in this zone, skip alternate doesn't matter grid_spacing_pixels = -1; } // Is truncated easting or northing larger than grid spacing? // If so, skip alternate labels // short_width_pixels+2 seems to work well. if (short_width_pixels+2>grid_spacing_pixels) { skip_alternate_label = TRUE; } else { skip_alternate_label = FALSE; } // Label the grid lines on the border. // Put easting along the bottom for easier correct ordering of easting and northing // by people who are reading the map. last_line_labeled = FALSE; for (ii=1; ii < (int)utm_grid.zone[Zone].ncols; ii++) { // label meridianal grid lines with easting if (utm_grid.zone[Zone].col[ii].npoints > 1) { // adjust up in case npoints goes far below the screen if (grid_spacing_pixels == 0) { continue; // Go to next iteration of for loop } bottom_point = (int)(screen_height/grid_spacing_pixels); if (bottom_point >= utm_grid.zone[Zone].col[ii].npoints) { bottom_point = utm_grid.zone[Zone].col[ii].npoints - 1; } if (skip_alternate_label==TRUE && last_line_labeled==TRUE) { last_line_labeled = FALSE; } else { xx = (utm_grid.zone[Zone].col[ii].points[bottom_point].x * scale_x) + NW_corner_longitude; yy = (utm_grid.zone[Zone].col[ii].points[bottom_point].y * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx, yy); // To display full precision to one meter, use: //xastir_snprintf(grid_label, // sizeof(grid_label), // "%06.0f0", // (float)((utm_grid_spacing_m/10) * roundf(easting/(utm_grid_spacing_m)))); // // Divide easting by utm_grid_spacing to make sure the line is labeled // correctly, and not a few meters off, and truncate to at least 100 m. xastir_snprintf(grid_label, sizeof(grid_label), "%05.0f", (float)((utm_grid_spacing_m/100) * roundf(easting/(utm_grid_spacing_m)))); // truncate the label to an appropriate level of precision for the grid if (utm_grid_spacing_m ==1000) { grid_label[4] = ' '; } if (utm_grid_spacing_m ==10000) { grid_label[3] = ' '; grid_label[4] = ' '; } if (utm_grid_spacing_m ==100000) { grid_label[2] = ' '; grid_label[3] = ' '; grid_label[4] = ' '; } if (coordinate_system == USE_MGRS) { convert_xastir_to_MGRS_str_components(mgrs_zone, strlen(mgrs_zone), mgrs_eastingL, sizeof(mgrs_eastingL), mgrs_northingL, sizeof(mgrs_northingL), &int_utmEasting, &int_utmNorthing, xx, yy, 0, mgrs_space_string, strlen(mgrs_space_string)); grid_label[0] = mgrs_eastingL[0]; grid_label[1] = mgrs_northingL[0]; if (mgrs_single_digraph==FALSE) { grid_label[1] = '_'; } } // draw each number at the bottom of the screen just to the right of the // relevant grid line at its location at the bottom of the screen //draw_nice_string(w,pixmap_final,0, // utm_grid.zone[Zone].col[i].points[bottom_point].x+1, // screen_height-2, // grid_label, // 0x10,easting_color,(int)strlen(grid_label)); // Don't overwrite the zone label, half the seven zeros string should give it room. // Don't draw the label if it will go off the left edge fo the screen. if ((utm_grid.zone[Zone].col[ii].points[bottom_point].x+1 > (string_width_pixels/2)) && (utm_grid.zone[Zone].col[ii].points[bottom_point].x+1 < (screen_width - string_width_pixels)) ) { // ok to draw the label last_line_labeled = TRUE; draw_rotated_label_text_to_target (w, 270, utm_grid.zone[Zone].col[ii].points[bottom_point].x+1, screen_height, sizeof(grid_label),colors[easting_color],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); } } } } last_line_labeled = FALSE; // put northing along the right border, again for easier correct ordering of easting and northing. for (ii=0; ii < (int)utm_grid.zone[Zone].nrows; ii++) { // label latitudinal grid lines with northing if (utm_grid.zone[Zone].row[ii].npoints > 1) { if (skip_alternate_label==TRUE && last_line_labeled==TRUE) { last_line_labeled = FALSE; } else { if (label_on_left==TRUE) { xx = (utm_grid.zone[Zone].row[ii].points[0].x * scale_x) + NW_corner_longitude; } else { xx = (utm_grid.zone[Zone].row[ii].points[utm_grid.zone[Zone].row[ii].npoints-1].x * scale_x) + NW_corner_longitude; } yy = (utm_grid.zone[Zone].row[ii].points[utm_grid.zone[Zone].row[ii].npoints-1].y * scale_y) + NW_corner_latitude; convert_xastir_to_UTM(&easting, &northing, zone_str, sizeof(zone_str), xx, yy); // To display to full 1 meter precision use: //xastir_snprintf(grid_label, // sizeof(grid_label), // "%06.0f0", // (float)((utm_grid_spacing_m/10) * roundf(northing/(utm_grid_spacing_m)))); // // Divide northing by utm grid spacing to make sure the line is labeled correctly // and displays zeroes in its least significant digits, and truncate to 100 m xastir_snprintf(grid_label, sizeof(grid_label), "%05.0f", (float)((utm_grid_spacing_m/100) * roundf(northing/(utm_grid_spacing_m)))); if (utm_grid_spacing_m ==1000) { grid_label[4] = ' '; } if (utm_grid_spacing_m ==10000) { grid_label[3] = ' '; grid_label[4] = ' '; } if (utm_grid_spacing_m ==100000) { grid_label[2] = ' '; grid_label[3] = ' '; grid_label[4] = ' '; } if (coordinate_system == USE_MGRS) { convert_xastir_to_MGRS_str_components(mgrs_zone, strlen(mgrs_zone), mgrs_eastingL, 3, mgrs_northingL, 3, &int_utmEasting, &int_utmNorthing, xx, yy, 0, mgrs_space_string, strlen(mgrs_space_string)); grid_label[0] = mgrs_eastingL[0]; if (mgrs_single_digraph==FALSE) { grid_label[0] = '_'; } grid_label[1] = mgrs_northingL[0]; } // Draw northing labels. // Draw each number just above the relevant grid line along the right side // of the screen. Don't write in the bottom border or off the top of the screen. if (label_on_left==TRUE) { // label northings on left border // don't overwrite the zone designator in the lower left border if ((utm_grid.zone[Zone].row[ii].points[0].y < (screen_height - border_width)) && (utm_grid.zone[Zone].row[ii].points[0].y > (string_width_pixels)) ) { last_line_labeled = TRUE; draw_rotated_label_text_to_target (w, 180, border_width, utm_grid.zone[Zone].row[ii].points[0].y, sizeof(grid_label),colors[northing_color],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); } } else { if (((utm_grid.zone[Zone].row[ii].points[utm_grid.zone[Zone].row[ii].npoints-1].y-1) < (screen_height - border_width)) && ((utm_grid.zone[Zone].row[ii].points[utm_grid.zone[Zone].row[ii].npoints-1].y-1) > (string_width_pixels)) ) { // label northings on right border last_line_labeled = TRUE; draw_rotated_label_text_to_target (w, 180, screen_width, utm_grid.zone[Zone].row[ii].points[utm_grid.zone[Zone].row[ii].npoints-1].y-1, sizeof(grid_label),colors[northing_color],grid_label,FONT_BORDER, pixmap_final, outline_border_labels, colors[outline_border_labels_color]); } } } } } // for i=0 to nrows } // if draw labeled grid border } // if utm_grid.zone[Zone] is non-empty } // for each zone in utm_grid.zone } // End of actually_draw_utm_minor_grid() function // Calculate the minor UTM grids. Called by draw_grid() below. // This function calculates and caches a within-zone UTM grid // for the current map view if one does not allready exist, it // then calls actually_draw_utm_minor_grid() function above to do // the drawing once the grid has been calculated. Zone boundaries // are drawn separately by draw_major_utm_mgrs_grid(). // // This routine appears to draw most of the UTM/UPS grid ok, with // the exceptions of: // // 1) Sometimes fails to draw vertical lines nearest zone // boundaries. // 2) Lines connect across zone boundaries in an incorrect manner, // jumping up one grid interval across the boundary. // 3) Segfaults near the special zone intersections as you zoom in. // // The code currently creates a col and row array per zone visible, // with XPoints malloced that contain the grid intersections in // screen coordinates. If the screen is zoomed or panned they are // recalculated. // // Perhaps we could do the same but with lat/long coordinates in the // future so that we'd only have to recalculate when a new Zone came // into view. We'd use the lat/long vector drawing programs above // then, with a possible slowdown due to more calculations if we're // not moving around. // // Returns: 0 if successful or nothing to draw // 1 if malloc error // 2 if iterations error // 3 if out of zones // 4 if realloc failure // int draw_minor_utm_mgrs_grid(Widget w) { long xx, yy, xx1, yy1; double e[4], n[4]; char place_str[10], zone_str[10]; int done = 0; int z1, z2, Zone, col, col_point, row, row_point, row_point_start; int iterations = 0; int finished_with_current_zone = 0; int ii, jj; float slope; int coordinate_system_backup = coordinate_system; col = 0; row = 0; col_point = 0; row_point = 0; row_point_start = 0; Zone = 0; // Set up for drawing zone grid(s) if (scale_x < 15) { utm_grid_spacing_m = 100; } else if (scale_x < 150) { utm_grid_spacing_m = 1000; } else if (scale_x < 1500) { utm_grid_spacing_m = 10000; } else if (scale_x < 3000) { utm_grid_spacing_m = 100000; } else { utm_grid_spacing_m = 0; // All done! Don't draw the minor grids. Major grids // have already been drawn by this point. return(0); } // Check hash to see if utm_grid is already set up if (utm_grid.hash.ul_x == NW_corner_longitude && utm_grid.hash.ul_y == NW_corner_latitude && utm_grid.hash.lr_x == SE_corner_longitude && utm_grid.hash.lr_y == SE_corner_latitude) { // XPoint arrays are already set up. Go draw the grid. actually_draw_utm_minor_grid(w); return(0); } // If we get to this point, we need to re-create the minor UTM/MGRS // grids as they haven't been set up yet or they don't match the // current view. // Clear the minor UTM/MGRS grid arrays. Alloc space for // the points in the grid structure. if (utm_grid_clear(1)) { // If we got here, we had a problem with malloc's return(1); } // Find top left point of current view xx = NW_corner_longitude; yy = NW_corner_latitude; // Note that the minor grid depends on the STANDARD six degree // UTM zones, not the UTM-Special/MGRS zones. Force our // calculations to use the standard zones. coordinate_system = USE_UTM; convert_xastir_to_UTM(&e[0], &n[0], place_str, sizeof(place_str), xx, yy); coordinate_system = coordinate_system_backup; n[0] += UTM_GRID_EQUATOR; // To work in southern hemisphere // Select starting point, NW corner of NW zone // Move the coordinates to the nearest subgrid intersection, // based on our current grid spacing. The grid intersection // we calculate here is northwest of our view's northwest // corner. e[0] /= utm_grid_spacing_m; e[0] = (double)((int)e[0] * utm_grid_spacing_m); n[0] /= utm_grid_spacing_m; n[0] = (double)((int)n[0] * utm_grid_spacing_m); n[0] += utm_grid_spacing_m; //WE7U // It appears that the horizontal grid lines get messed up in cases // where the top horizontal line isn't in view on it's left end. // That's a major clue! Read the comment below (again with a "WE7U" // tag). The problem occurs at the point where we copy the last // point from the previous grid over to the first point of a new // grid. That can cause us to be off by one, as for the grid on the // left, the top horizontal line _is_ in view on the left. We end // up connecting the wrong horizontal lines together because of this // mismatch, but again, only if the top horizontal line on the left // grid is above the current view. // // It also appears that the vertical lines that are missing in some // cases are on the right of the zone boundary. This is probably // because the top of that line doesn't go to the top of the view. // On views where it does, the line is drawn. I assume this is // because we're drawing from NW corner to the right, and then down, // which would cause that line to be skipped if it's not present on // the first line? e[1] = e[0]; n[1] = n[0]; ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// // Start filling in the row/column arrays of grid intersections while (!done) { XPoint *temp_point; // Here's our escape in case we get stuck in this loop. // We can go through this loop multiple times for each // zone though, depending on our grid spacing. 64 rows * 64 // colums * 8 points each = 32768, which gives us our upper // limit. if (iterations++ > 32768) { fprintf(stderr, "draw_minor_utm_mgrs_grid() looped too many times, escaping.\n"); utm_grid_clear(1); return(2); } if (finished_with_current_zone) { // Set up to compute the next zone xx = NW_corner_longitude + ((utm_grid.zone[Zone].boundary_x + 1) * scale_x); yy = NW_corner_latitude; // Note that the minor grid depends on the STANDARD six // degree UTM zones, not the UTM-Special/MGRS zones. // Force our calculations to use the standard zones. coordinate_system = USE_UTM; convert_xastir_to_UTM(&e[0], &n[0], place_str, sizeof(place_str), xx, yy); coordinate_system = coordinate_system_backup; n[0] += UTM_GRID_EQUATOR; // To work in southern hemisphere // Fix the coordinates to the nearest subgrid intersection based on // our current grid spacing. Bump both the easting and northing up // by one subgrid. e[0] /= utm_grid_spacing_m; e[0] = (double)((int)e[0] * utm_grid_spacing_m); e[0] += utm_grid_spacing_m; n[0] /= utm_grid_spacing_m; n[0] = (double)((int)n[0] * utm_grid_spacing_m); n[0] += utm_grid_spacing_m; e[1] = e[0]; n[1] = n[0]; #ifdef UTM_DEBUG fprintf(stderr,"\nFinished Zone=%d\n", Zone); #endif // We're all done with the current zone. Increment // to the next zone and set up to calculate its // points. Zone++; #ifdef UTM_DEBUG fprintf(stderr,"\nstarting Zone=%d, row_point_start=1\n", Zone); #endif row_point = row_point_start = 1; col = row = col_point = 0; finished_with_current_zone = 0; if (Zone >= UTM_GRID_MAX_ZONES) { fprintf(stderr,"Error: Zone=%d: out of zones!\n", Zone); Zone = 0; done = 1; utm_grid_clear(1); return(3); } } // End of if(finished_with_current_zone) // Note that the minor grid depends on the STANDARD six // degree UTM zones, not the UTM-Special/MGRS zones. Force // our calculations to use the standard zones. coordinate_system = USE_UTM; convert_UTM_to_xastir(e[1], n[1]-UTM_GRID_EQUATOR, place_str, &xx, &yy); coordinate_system = coordinate_system_backup; xx1 = xx; // Save yy1 = yy; // Save // Note that the minor grid depends on the STANDARD six // degree UTM zones, not the UTM-Special/MGRS zones. Force // our calculations to use the standard zones. coordinate_system = USE_UTM; convert_xastir_to_UTM(&e[2], &n[2], zone_str, sizeof(zone_str), xx, yy); coordinate_system = coordinate_system_backup; n[2] += UTM_GRID_EQUATOR; xx = (xx - NW_corner_longitude) / scale_x; yy = (yy - NW_corner_latitude) / scale_y; // Not all columns (and maybe rows) will start at point // 0 if (utm_grid.zone[Zone].col[col].firstpoint == UTM_GRID_RC_EMPTY) { utm_grid.zone[Zone].col[col].firstpoint = l16(col_point); #ifdef UTM_DEBUG fprintf(stderr,"col[%d] started at point %d\n", col, col_point); #endif } if (utm_grid.zone[Zone].row[row].firstpoint == UTM_GRID_RC_EMPTY) { utm_grid.zone[Zone].row[row].firstpoint = l16(row_point); #ifdef UTM_DEBUG fprintf(stderr,"row[%d] started at point %d\n", row, row_point); #endif } // Check to see if we need to alloc more space for // column points ii = utm_grid.zone[Zone].col[col].npoints + utm_grid.zone[Zone].col[col].firstpoint + 1; if (ii > utm_grid.zone[Zone].col[col].nalloced) { #ifdef UTM_DEBUG_ALLOC fprintf(stderr,"i=%d n=%d realloc(utm_grid.zone[%d].col[%d].points, ", ii, utm_grid.zone[Zone].col[col].nalloced, Zone, col); #endif ii = ((ii / UTM_GRID_DEF_NALLOCED) + 1) * UTM_GRID_DEF_NALLOCED; #ifdef UTM_DEBUG_ALLOC fprintf(stderr,"%d)\n", ii); #endif temp_point = realloc(utm_grid.zone[Zone].col[col].points, ii * sizeof(XPoint)); if (temp_point) { utm_grid.zone[Zone].col[col].points = temp_point; utm_grid.zone[Zone].col[col].nalloced = ii; } else { puts("realloc FAILED!"); (void)utm_grid_clear(1); // Clear arrays and allocate memory for points return(4); } } // Check to see if we need to alloc more space for row // points ii = utm_grid.zone[Zone].row[row].npoints + utm_grid.zone[Zone].row[row].firstpoint + 1; if (ii > utm_grid.zone[Zone].row[row].nalloced) { #ifdef UTM_DEBUG_ALLOC fprintf(stderr,"i=%d n=%d realloc(utm_grid.zone[%d].row[%d].points, ", ii, utm_grid.zone[Zone].row[row].nalloced, Zone, row); #endif ii = ((ii / UTM_GRID_DEF_NALLOCED) + 1) * UTM_GRID_DEF_NALLOCED; #ifdef UTM_DEBUG_ALLOC fprintf(stderr,"%d)\n", ii); #endif temp_point = realloc(utm_grid.zone[Zone].row[row].points, ii * sizeof(XPoint)); if (temp_point) { utm_grid.zone[Zone].row[row].points = temp_point; utm_grid.zone[Zone].row[row].nalloced = ii; } else { puts("realloc FAILED!"); (void)utm_grid_clear(1); // Clear arrays and allocate memory for points return(4); } } // Here we check to see whether we are inserting points // that are greater than about +/- 32767. If so, // truncate at that. This prevents XDrawLines() from // going nuts and drawing hundreds of extra lines. // xx = l16(xx); yy = l16(yy); utm_grid.zone[Zone].col[col].points[col_point].x = l16(xx); utm_grid.zone[Zone].col[col].points[col_point].y = l16(yy); utm_grid.zone[Zone].col[col].npoints++; utm_grid.zone[Zone].row[row].points[row_point].x = l16(xx); utm_grid.zone[Zone].row[row].points[row_point].y = l16(yy); utm_grid.zone[Zone].row[row].npoints++; #ifdef UTM_DEBUG fprintf(stderr,"utm_grid.zone[%d].col[%d].points[%d] = [ %ld,%ld ] npoints=%d\n", Zone, col, col_point, xx, yy, utm_grid.zone[Zone].col[col].npoints); fprintf(stderr,"utm_grid.zone[%d].row[%d].points[%d] = [ %ld,%ld ]\n", Zone, row, row_point, xx, yy); #endif col++; row_point++; if (col >= UTM_GRID_MAX_COLS_ROWS) { finished_with_current_zone++; } z1 = atoi(place_str); z2 = atoi(zone_str); if (z1 != z2 || xx > screen_width) // We hit a boundary { #ifdef UTM_DEBUG_VERB if (z1 != z2) { fprintf(stderr,"Zone boundary! \"%s\" -> \"%s\"\n", place_str, zone_str); } else { puts("Screen boundary!"); } #endif //#warning //#warning I suspect that I should not use just col for the following. //#warning if (col-2 >= 0) slope = (float)(yy - utm_grid.zone[Zone].col[col-2].points[col_point].y) / (float)(xx - utm_grid.zone[Zone].col[col-2].points[col_point].x + 0.001); else { slope = 0.0; } if (xx > screen_width) { xx1 = screen_width; } else { // 360,000 Xastir units equals one degree. This // code appears to be adjusting xx1 to a major // zone edge. xx1 = (xx1 / (6 * 360000)) * 6 * 360000; xx1 = (xx1 - NW_corner_longitude) / scale_x; } utm_grid.zone[Zone].boundary_x = xx1; yy1 = yy - (xx - xx1) * slope; #ifdef UTM_DEBUG fprintf(stderr,"_tm_grid.zone[%d].col[%d].points[%d] = [ %ld,%ld ]\n", Zone, col-1, col_point, xx1, yy1); fprintf(stderr,"_tm_grid.zone[%d].row[%d].points[%d] = [ %ld,%ld ]\n", Zone, row, row_point-1, xx1, yy1); #endif if (col-1 >= 0 && row_point-1 >= 0) { utm_grid.zone[Zone].col[col-1].points[col_point].x = l16(xx1); utm_grid.zone[Zone].col[col-1].points[col_point].y = l16(yy1); utm_grid.zone[Zone].row[row].points[row_point-1].x = l16(xx1); utm_grid.zone[Zone].row[row].points[row_point-1].y = l16(yy1); if (z1 != z2 && Zone+1 < UTM_GRID_MAX_ZONES) { // copy over last points to start off new // zone #ifdef UTM_DEBUG fprintf(stderr,"ztm_grid.zone[%d].row[%d].points[%d] = [ %ld,%ld ]\n", Zone+1, row, 0, xx1, yy1); #endif //WE7U // This is where we can end up linking up/down one grid width // between zones!!! Without it though, we end up have a blank // section to the right of the zone boundary. Perhaps we could do // this here, but when we get the next points calculated, we could // check to see if we're off by about one grid width in the vertical // direction. If so, shift the initial point by that amount? // // Another possibility might be to draw bottom-to-top if in northern // hemisphere, and top-to-bottom if in southern hemisphere. That // way we'd have the max amount of lines present when we start, and // some might peter out as we draw along N/S. Looking at the // southern hemisphere right now though, that method doesn't appear // to work. We get the same problems there even though we're // drawing top to bottom. // utm_grid.zone[Zone+1].row[row].points[0].x = l16(xx1); utm_grid.zone[Zone+1].row[row].points[0].y = l16(yy1); utm_grid.zone[Zone+1].row[row].firstpoint = 0; utm_grid.zone[Zone+1].row[row].npoints = 1; } } // Check last built row to see if it is all off // screen finished_with_current_zone++; // Assume we're done with this zone for (ii=0; ii < utm_grid.zone[Zone].row[row].npoints; ii++) { if (utm_grid.zone[Zone].row[row].points[ii].y <= screen_height) { finished_with_current_zone = 0; // Some points were within the zone, keep computing } } e[1] = e[0]; // carriage return n[1] -= utm_grid_spacing_m; // line feed // Yea, your comments are real funny Olivier... Gets the point // across though! row++; if (row >= UTM_GRID_MAX_COLS_ROWS) { finished_with_current_zone++; } utm_grid.zone[Zone].ncols = max_i(col, utm_grid.zone[Zone].ncols); utm_grid.zone[Zone].nrows = max_i(row, utm_grid.zone[Zone].nrows); col = 0; row_point = row_point_start; col_point++; if (n[1] < 0) { fprintf(stderr,"n[1] < 0\n"); finished_with_current_zone++; } if (finished_with_current_zone && xx > screen_width) { done = 1; } // Go to next iteration of while loop (skip next statement) continue; } e[1] += utm_grid_spacing_m; } // End of while (done) loop ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// //fprintf(stderr, "After while loop\n"); // utm_grid.zone[] now contains an array of points marking fine grid // line intersections for parts of 1 to 4 zones that appear on // the screen. Each utm_grid.zone[] is a vertical stripe, and may include // more than one zone letter, e.g. zone[0] might include 15U and 15T, // while zone[1] might include 16U and 16T. //#define UTM_DEBUG_VERB for (Zone=0; Zone < UTM_GRID_MAX_ZONES; Zone++) { #ifdef UTM_DEBUG_VERB fprintf(stderr,"\nutm_grid.zone[%d].ncols=%d\nutm_grid.zone[%d].nrows=%d\n", Zone, utm_grid.zone[Zone].ncols, Zone, utm_grid.zone[Zone].nrows); #endif // Cleanup columns for (ii=0; ii < (int)utm_grid.zone[Zone].ncols; ii++) { int np = utm_grid.zone[Zone].col[ii].npoints; int fp = utm_grid.zone[Zone].col[ii].firstpoint; int nbp = 0; #ifdef UTM_DEBUG_VERB fprintf(stderr,"utm_grid.zone[%d].col[%d].npoints=%d .firstpoint=%d\n", Zone, ii, np, fp); if (np < 2) { puts(" Not enough points!"); } else { puts(""); } for (jj=fp; jj < fp+np; jj++) { fprintf(stderr," col[%d].points[%d] = [ %d, %d ]", ii, jj, utm_grid.zone[Zone].col[ii].points[jj].x, utm_grid.zone[Zone].col[ii].points[jj].y); if (utm_grid.zone[Zone].col[ii].points[jj].x == utm_grid.zone[Zone].boundary_x) { puts(" Boundary"); } else { puts(""); } } #endif for (jj=fp; jj < fp+np; jj++) { if (utm_grid.zone[Zone].col[ii].points[jj].x == utm_grid.zone[Zone].boundary_x) { nbp++; } else if (nbp > 0) // We had a boundary point, but not anymore { fp = utm_grid.zone[Zone].col[ii].firstpoint = l16(jj - 1); //fprintf(stderr,"np:%d, jj:%d\n",np,jj); // This can result in negative numbers! np = utm_grid.zone[Zone].col[ii].npoints = np - jj + 1; //fprintf(stderr,"new np:%d\n",np); if (np < 0) { np = 0; // Prevents segfaults in // XDrawLines() and memmove() // below. } break; // Exit from for loop } if (nbp == np) // All points are boundary points { fp = utm_grid.zone[Zone].col[ii].firstpoint = 0; np = utm_grid.zone[Zone].col[ii].npoints = 0; } } // What's the below code doing? Can get a segfault without this in // the XDrawLines() functions below (fixed by making npoints an int // instead of an unsigned int). Sometimes we get a segfault right // here due to the memmove() function. In one such case, np was -2. // Latest code keeps some lines from getting drawn, but at least we // don't get a segfault. // if (fp > 0) { if (np > 0) { memmove(&utm_grid.zone[Zone].col[ii].points[0], &utm_grid.zone[Zone].col[ii].points[fp], np * sizeof(XPoint)); fp = utm_grid.zone[Zone].col[ii].firstpoint = 0; } else { //fprintf(stderr,"draw_minor_utm_mgrs_grid: ii:%d, np:%d, size:%d\n",ii,np,sizeof(XPoint)); //fprintf(stderr,"Problem1: in draw_minor_utm_mgrs_grid() memmove, np was %d. Skipping memmove.\n",np); } } #ifdef UTM_DEBUG_VERB fprintf(stderr,"_tm_grid.zone[%d].col[%d].npoints=%d.firstpoint=%d\n", Zone, ii, np, fp); for (jj=fp; jj < fp+np; jj++) { fprintf(stderr," col[%d].points[%d] = [ %d, %d ]", ii, jj, utm_grid.zone[Zone].col[ii].points[jj].x, utm_grid.zone[Zone].col[ii].points[jj].y); if (utm_grid.zone[Zone].col[ii].points[jj].x == utm_grid.zone[Zone].boundary_x) { puts(" Boundary"); } else { puts(""); } } puts(""); #endif } // Cleanup rows for (ii=0; ii < (int)utm_grid.zone[Zone].nrows; ii++) { int np = utm_grid.zone[Zone].row[ii].npoints; int fp = utm_grid.zone[Zone].row[ii].firstpoint; #ifdef UTM_DEBUG_VERB fprintf(stderr,"utm_grid.zone[%d].row[%d].npoints=%d.firstpoint=%d\n", Zone, ii, np, fp); if (np < 2) { puts(" Not enough points!"); } else { puts(""); } #endif // What's this doing? This appears to be important, as things get // really messed up if it's commented out. if (fp > 0) { if (np > 0) { memmove(&utm_grid.zone[Zone].row[ii].points[0], &utm_grid.zone[Zone].row[ii].points[fp], np * sizeof(XPoint)); fp = utm_grid.zone[Zone].row[ii].firstpoint = 0; } else { //fprintf(stderr,"draw_minor_utm_mgrs_grid: ii:%d, np:%d, size:%d\n",ii,np,sizeof(XPoint)); //fprintf(stderr,"Problem2: in draw_minor_utm_mgrs_grid() memmove, np was %d. Skipping memmove.\n",np); } } #ifdef UTM_DEBUG_VERB for (jj=fp; jj < fp+np; jj++) { fprintf(stderr," row[%d].points[%d] = [ %d, %d ]\n", ii, jj, utm_grid.zone[Zone].row[ii].points[jj].x, utm_grid.zone[Zone].row[ii].points[jj].y); } #endif } } // Rows and columns ready to go so setup hash utm_grid.hash.ul_x = NW_corner_longitude; utm_grid.hash.ul_y = NW_corner_latitude; utm_grid.hash.lr_x = SE_corner_longitude; utm_grid.hash.lr_y = SE_corner_latitude; // XPoint arrays are set up. Go draw the grid. actually_draw_utm_minor_grid(w); return(0); } // End of draw_minor_utm_mgrs_grid() function //***************************************************************** // draw_grid() // // Draws a lat/lon or UTM/UPS grid on top of the view. // //***************************************************************** void draw_grid(Widget w) { int half; // Center of the white lines used to draw the borders int border_width = 14; // The width of the border to draw around the // map to place labeled tick marks into // should be an even number. // The default here is overidden by the border fontsize. if (!long_lat_grid) // We don't wish to draw a map grid { return; } if (draw_labeled_grid_border==TRUE) { // Determine how wide the border should be. border_width = get_border_width(w); half = border_width/2; // draw a white border around the map. (void)XSetLineAttributes(XtDisplay(w), gc, border_width, LineSolid, CapRound, JoinRound); (void)XSetForeground(XtDisplay(w), gc, colors[border_foreground_color]); // white (void)XDrawLine(XtDisplay(w), pixmap_final, gc, 0, l16(half), l16(screen_width), l16(half)); (void)XDrawLine(XtDisplay(w), pixmap_final, gc, l16(half), 0, l16(half), l16(screen_height)); (void)XDrawLine(XtDisplay(w), pixmap_final, gc, 0, l16(screen_height-half), l16(screen_width), l16(screen_height-half)); (void)XDrawLine(XtDisplay(w), pixmap_final, gc, l16(screen_width-half), 0, l16(screen_width-half), l16(screen_height)); } // Set the line width in the GC to 2 pixels wide for the larger // UTM grid and the complete Lat/Long grid. (void)XSetLineAttributes (XtDisplay (w), gc_tint, 2, LineOnOffDash, CapButt,JoinMiter); (void)XSetForeground (XtDisplay (w), gc_tint, colors[0x27]); (void)XSetFunction (XtDisplay (da), gc_tint, GXxor); if (coordinate_system == USE_UTM || coordinate_system == USE_UTM_SPECIAL || coordinate_system == USE_MGRS) { int ret_code; //draw_vector_ll(w, -5.0, -5.0, 5.0, 5.0, gc_tint, pixmap_final, 0); //draw_vector_ll(w, 5.0, 5.0, -5.0, -5.0, gc_tint, pixmap_final, 0); // Draw major UTM/MGRS zones draw_major_utm_mgrs_grid(w); // Draw minor UTM/MGRS zones ret_code = draw_minor_utm_mgrs_grid(w); if (ret_code) { fprintf(stderr, "Encountered problem %d while calculating minor utm grid!\n", ret_code); } } // End of UTM grid section else // Lat/Long coordinate system, draw lat/long lines { draw_complete_lat_lon_grid(w); } // End of Lat/Long section } // End of draw_grid() /********************************************************** * get_map_ext() * * Returns the extension for the filename. We use this to * determine which sort of map file it is. **********************************************************/ char *get_map_ext (char *filename) { int len; int i; char *ext; ext = NULL; len = (int)strlen (filename); for (i = len; i >= 0; i--) { if (filename[i] == '.') { ext = filename + (i + 1); break; } } return (ext); } /********************************************************** * get_map_dir() * * Used to snag just the pathname from a complete filename. * Modifies input parameter "fullpath". **********************************************************/ char *get_map_dir (char *fullpath) { int len; int i; len = (int)strlen (fullpath); for (i = len; i >= 0; i--) { if (fullpath[i] == '/') { fullpath[i + 1] = '\0'; break; } } return (fullpath); } /*********************************************************** * map_visible() * * Tests whether a particular path/filename is within our * current view. We use this to decide whether to plot or * skip a particular image file (major speed-up!). * Input coordinates are in the Xastir coordinate system. * * Had to fix a bug here where the viewport glanced over the * edge of the earth, causing strange results like this. * Notice the View Edges Top value is out of range: * * * Bottom Top Left Right * View Edges: 31,017,956 4,290,923,492 35,971,339 90,104,075 * Map Edges: 12,818,482 12,655,818 64,079,859 64,357,110 * * Left map boundary inside view * Right map boundary inside view * map_inside_view: 1 view_inside_map: 0 parallel_edges: 0 * Map not within current view. * Skipping map: /usr/local/share/xastir/maps/tif/uk/425_0525_bng.tif * * * I had to check for out-of-bounds numbers for the viewport and * set them to min or max values so that this function always * works properly. Here are the bounds of the earth (Xastir * Coordinate System): * * 0 (90 deg. or 90N) * * 0 (-180 deg. or 180W) 129,600,000 (180 deg. or 180E) * * 64,800,000 (-90 deg. or 90S) * ***********************************************************/ int map_visible (unsigned long map_max_y, // bottom_map_boundary unsigned long map_min_y, // top_map_boundary unsigned long map_min_x, // left_map_boundary unsigned long map_max_x) // right_map_boundary) { { //fprintf(stderr,"map_visible\n"); // From computation geometry equations, intersection of two line // segments, they use the bounding box for two lines. This is // the same as what we want to do: // // http://www.cs.kent.edu/~dragan/AdvAlg/CompGeom-2x1.pdfa // http://www.gamedev.net/reference/articles/article735.asp // // The quick rejection algorithm: // if (NW_corner_latitude > (long)map_max_y) { if (debug_level & 16) { fprintf(stderr, "map_visible, rejecting: NW_corner_latitude:%ld > map_max_y:%ld\n", NW_corner_latitude, map_max_y); fprintf(stderr, "\tmap or object is above viewport\n"); } return(0); } if ((long)map_min_y > SE_corner_latitude) { if (debug_level & 16) { fprintf(stderr, "map_visible, rejecting: map_min_y:%ld > SE_corner_latitude:%ld\n", map_min_y, SE_corner_latitude); fprintf(stderr, "\tmap or object is below viewport\n"); } return(0); } if (NW_corner_longitude > (long)map_max_x) { if (debug_level & 16) { fprintf(stderr, "map_visible, rejecting: NW_corner_longitude:%ld > map_max_x:%ld\n", NW_corner_longitude, map_max_x); fprintf(stderr, "\tmap or object is left of viewport\n"); } return(0); } if ((long)map_min_x > SE_corner_longitude) { if (debug_level & 16) { fprintf(stderr, "map_visible, rejecting: map_min_x:%ld > SE_corner_longitude:%ld\n", map_min_x, SE_corner_longitude); fprintf(stderr, "\tmap or object is right of viewport\n"); } return(0); } return (1); // At least part of the map is on-screen } ///////////////////////////////////////////////////////////////////// // get_viewport_lat_lon(double *xmin, double *ymin, double* xmax, double *ymax) // Simply returns the floating point corners of the map display. ///////////////////////////////////////////////////////////////////// void get_viewport_lat_lon(double *xmin, double *ymin, double* xmax, double *ymax) { *xmin=(double)f_NW_corner_longitude; *ymin=(double)f_SE_corner_latitude; *xmax=(double)f_SE_corner_longitude; *ymax=(double)f_NW_corner_latitude; } ///////////////////////////////////////////////////////////////////// // map_inside_viewport_lat_lon() // Returns 1 if the given set of xmin,xmax, ymin,ymax defines a // rectangle entirely contained in the current viewport (as opposed to // merely partially overlapping it. Returns zero otherwise. ///////////////////////////////////////////////////////////////////// int map_inside_viewport_lat_lon(double map_min_y, double map_max_y, double map_min_x, double map_max_x) { int retval=0; if (map_min_x >= f_NW_corner_longitude && map_min_y >= f_SE_corner_latitude && map_max_x <= f_SE_corner_longitude && map_max_y <= f_NW_corner_latitude) { retval=1; } return (retval); } ///////////////////////////////////////////////////////////////////// // map_visible_lat_lon() // // We have the center of the view in floating point format: // // float f_center_longitude; // Floating point map center longitude // float f_center_latitude; // Floating point map center latitude // // So we just need to compute the top/bottom/left/right using those // values and the scale_x/scale_y values before doing the compare. // // y scaling in 1/100 sec per pixel // x scaling in 1/100 sec per pixel, calculated from scale_y // // // 0 (90 deg. or 90N) // // 0 (-180 deg. or 180W) 129,600,000 (180 deg. or 180E) // // 64,800,000 (-90 deg. or 90S) // // ******************* ******************* max_y // *NW * + * * + // * * * * // * * * * // * View * latitude (y) * Map * // * * * * // * * * * // * SE* - * * - // ******************* ******************* min_y // - longitude(x) + - min_x max_x + ///////////////////////////////////////////////////////////////////// int map_visible_lat_lon (double map_min_y, // f_bottom_map_boundary double map_max_y, // f_top_map_boundary double map_min_x, // f_left_map_boundary double map_max_x) // f_right_map_boundary { //fprintf(stderr,"map_visible_lat_lon\n"); // From computation geometry equations, intersection of two line // segments, they use the bounding box for two lines. This is // the same as what we want to do: // // http://www.cs.kent.edu/~dragan/AdvAlg/CompGeom-2x1.pdfa // http://www.gamedev.net/reference/articles/article735.asp // // The quick rejection algorithm: // if (map_max_y < f_SE_corner_latitude ) { return(0); // map below view } if (map_max_x < f_NW_corner_longitude) { return(0); // map left of view } if (map_min_y > f_NW_corner_latitude ) { return(0); // view below map } if (map_min_x > f_SE_corner_longitude) { return(0); // view left of map } return (1); // Draw this map onto the screen } /********************************************************** * draw_label_text() * * Does what it says. Used to draw strings onto the * display. **********************************************************/ void draw_label_text (Widget w, int x, int y, int label_length, int color, char *label_text) { // This draws a gray background rectangle upon which we draw the text. // Probably not needed. It ends up obscuring details underneath. //(void)XSetForeground (XtDisplay (w), gc, colors[0x0ff]); //(void)XFillRectangle (XtDisplay (w), pixmap, gc, x - 1, (y - 10),(label_length * 6) + 2, 11); (void)XSetForeground (XtDisplay (w), gc, color); (void)XDrawString (XtDisplay (w), pixmap, gc, x, y, label_text, label_length); } // Must make sure that fonts are not loaded again and again, as this // takes a big chunk of memory each time. Can you say "memory // leak"? XFontStruct *rotated_label_font[FONT_MAX]= {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; char rotated_label_fontname[FONT_MAX][MAX_LABEL_FONTNAME]; static char current_rotated_label_fontname[FONT_MAX][sizeof(rotated_label_fontname)] = {"","","","","","","","",""}; /********************************************************** * draw_rotated_label_text_common() * call through wrappers: * draw_rotated_label_text_to_pixmap() * draw_rotated_label_text() * draw_centered_label_text() * * Does what it says. Used to draw strings onto the * display. * * Use "xfontsel" or other tools to figure out what fonts * to use here. * * Paramenters: * target_pixmap specifies the pixmap the text is to be drawn to. * draw_outline specifies whether a 1 pixel outline around the * text, TRUE to draw outline. * outline_bg_color is the color of the outline. * color is the color of the text inside the outline, or the * color of the text itself if no outline is added. **********************************************************/ /* common code used by the two entries --- a result of retrofitting a new feature (centered) */ static void draw_rotated_label_text_common (Widget w, float my_rotation, int x, int y, int UNUSED(label_length), int color, char *label_text, int align, int fontsize, Pixmap target_pixmap, int draw_outline, int outline_bg_color) { // XPoint *corner; // int i; int x_outline; int y_outline; // Do some sanity checking if (fontsize < 0 || fontsize >= FONT_MAX) { fprintf(stderr,"Font size is out of range: %d\n", fontsize); return; } /* see if fontname has changed */ if (rotated_label_font[fontsize] && strcmp(rotated_label_fontname[fontsize],current_rotated_label_fontname[fontsize]) != 0) { XFreeFont(XtDisplay(w),rotated_label_font[fontsize]); rotated_label_font[fontsize] = NULL; xastir_snprintf(current_rotated_label_fontname[fontsize], sizeof(rotated_label_fontname), "%s", rotated_label_fontname[fontsize]); } /* load font */ if(!rotated_label_font[fontsize]) { rotated_label_font[fontsize]=(XFontStruct *)XLoadQueryFont(XtDisplay (w), rotated_label_fontname[fontsize]); if (rotated_label_font[fontsize] == NULL) // Couldn't get the font!!! { fprintf(stderr,"draw_rotated_label_text: Couldn't get font %s\n", rotated_label_fontname[fontsize]); return; } } if (draw_outline) { // make outline style (void)XSetForeground(XtDisplay(w),gc,outline_bg_color); // Draw the string repeatedly with 1 pixel offsets in the // background color to make an outline. for (x_outline=-1; x_outline<2; x_outline++) { for (y_outline=-1; y_outline<2; y_outline++) { // draws one extra copy at x,y (void)XRotDrawAlignedString(XtDisplay (w), rotated_label_font[fontsize], my_rotation, target_pixmap, gc, x+x_outline, y+y_outline, label_text, align); } } } // Code to determine the bounding box corner points for the rotated text // corner = XRotTextExtents(w,rotated_label_font,my_rotation,x,y,label_text,BLEFT); // for (i=0;i<5;i++) { // fprintf(stderr,"%d,%d\t",corner[i].x,corner[i].y); // } // fprintf(stderr,"\n"); (void)XSetForeground (XtDisplay (w), gc, color); //fprintf(stderr,"%0.1f\t%s\n",my_rotation,label_text); (void)XRotDrawAlignedString(XtDisplay (w), rotated_label_font[fontsize], my_rotation, target_pixmap, gc, x, y, label_text, align); } // Find the pixel length of an unrotated string in the rotated_label_font. // Parameters: // w - the XtDisplay. // label_text - the string of which the length is to be found. // fontsize - the fontsize in the rotated_label_font in which the string // is to be rendered. // Returns: the length in pixels of the string, -1 on an error. int get_rotated_label_text_length_pixels(Widget w, char *label_text, int fontsize) { int dir, asc, desc; // parameters returned by XTextExtents, but not used here. XCharStruct overall; // description of the space occupied by the string. int return_value; // value to return int got_font; // flag indicating that a font is available return_value = -1; got_font = TRUE; /* load font */ if(!rotated_label_font[fontsize]) { rotated_label_font[fontsize]=(XFontStruct *)XLoadQueryFont(XtDisplay (w), rotated_label_fontname[fontsize]); if (rotated_label_font[fontsize] == NULL) // Couldn't get the font!!! { fprintf(stderr,"get_rotated_label_text_length_pixels: Couldn't get font %s\n", rotated_label_fontname[fontsize]); got_font = FALSE; } } if (got_font) { // find out the width in pixels of the unrotated label_text string. XTextExtents(rotated_label_font[fontsize], label_text, strlen(label_text), &dir, &asc, &desc, &overall); return_value = overall.width; } return return_value; } // Find the pixel height of an unrotated string in the rotated_label_font. // Parameters: // w - the XtDisplay. // label_text - the string of which the length is to be found. // fontsize - the fontsize in the rotated_label_font in which the string // is to be rendered. // Returns: the height in pixels of the string, -1 on an error. int get_rotated_label_text_height_pixels(Widget w, char *label_text, int fontsize) { int dir, asc, desc; // parameters returned by XTextExtents, but not used here. XCharStruct overall; // description of the space occupied by the string. int return_value; // value to return int got_font; // flag indicating that a font is available return_value = -1; got_font = TRUE; /* load font */ if(!rotated_label_font[fontsize]) { rotated_label_font[fontsize]=(XFontStruct *)XLoadQueryFont(XtDisplay (w), rotated_label_fontname[fontsize]); if (rotated_label_font[fontsize] == NULL) // Couldn't get the font!!! { fprintf(stderr,"get_rotated_label_text_height_pixels: Couldn't get font %s\n", rotated_label_fontname[fontsize]); got_font = FALSE; } } if (got_font) { // find out the width in pixels of the unrotated label_text string. XTextExtents(rotated_label_font[fontsize], label_text, strlen(label_text), &dir, &asc, &desc, &overall); return_value = overall.ascent + overall.descent; } return return_value; } // Draw a rotated label onto the specified pixmap. // Wrapper for draw_rotated_label_text-common(). void draw_rotated_label_text_to_target (Widget w, int rotation, int x, int y, int label_length, int color, char *label_text, int fontsize, Pixmap target_pixmap, int draw_outline, int outline_bg_color) { float my_rotation = (float)((-rotation)-90); if ( ( (my_rotation < -90.0) && (my_rotation > -270.0) ) || ( (my_rotation > 90.0) && (my_rotation < 270.0) ) ) { my_rotation = my_rotation + 180.0; (void)draw_rotated_label_text_common(w, my_rotation, x, y, label_length, color, label_text, BRIGHT, fontsize, target_pixmap, draw_outline, outline_bg_color); } else { (void)draw_rotated_label_text_common(w, my_rotation, x, y, label_length, color, label_text, BLEFT, fontsize, target_pixmap, draw_outline, outline_bg_color); } } void draw_rotated_label_text (Widget w, int rotation, int x, int y, int label_length, int color, char *label_text, int fontsize) { float my_rotation = (float)((-rotation)-90); if ( ( (my_rotation < -90.0) && (my_rotation > -270.0) ) || ( (my_rotation > 90.0) && (my_rotation < 270.0) ) ) { my_rotation = my_rotation + 180.0; (void)draw_rotated_label_text_common(w, my_rotation, x, y, label_length, color, label_text, BRIGHT, fontsize, pixmap, 0, 0); } else { (void)draw_rotated_label_text_common(w, my_rotation, x, y, label_length, color, label_text, BLEFT, fontsize, pixmap, 0, 0); } } void draw_centered_label_text (Widget w, int rotation, int x, int y, int label_length, int color, char *label_text, int fontsize) { float my_rotation = (float)((-rotation)-90); (void)draw_rotated_label_text_common(w, my_rotation, x, y, label_length, color, label_text, BCENTRE, fontsize, pixmap, 0, 0); } static void Print_postscript_destroy_shell(Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) ) { Widget shell = (Widget) clientData; char *temp_ptr; XtPopdown(shell); begin_critical_section(&print_postscript_dialog_lock, "maps.c:Print_postscript_destroy_shell" ); if (print_postscript_dialog) { // Snag the path to the printer program from the print dialog temp_ptr = XmTextFieldGetString(printer_data); xastir_snprintf(printer_program, sizeof(printer_program), "%s", temp_ptr); XtFree(temp_ptr); (void)remove_trailing_spaces(printer_program); // Check for empty variable if (printer_program[0] == '\0') { #ifdef LPR_PATH // Path to LPR if defined xastir_snprintf(printer_program, sizeof(printer_program), "%s", LPR_PATH); #else // LPR_PATH // Empty path printer_program[0]='\0'; #endif // LPR_PATH } //fprintf(stderr,"%s\n", printer_program); // Snag the path to the previewer program from the print dialog temp_ptr = XmTextFieldGetString(previewer_data); xastir_snprintf(previewer_program, sizeof(previewer_program), "%s", temp_ptr); XtFree(temp_ptr); (void)remove_trailing_spaces(previewer_program); // Check for empty variable if (previewer_program[0] == '\0') { #ifdef GV_PATH // Path to GV if defined xastir_snprintf(previewer_program, sizeof(previewer_program), "%s", GV_PATH); #else // GV_PATH // Empty string previewer_program[0] = '\0'; #endif // GV_PATH } //fprintf(stderr,"%s\n", previewer_program); } XtDestroyWidget(shell); print_postscript_dialog = (Widget)NULL; end_critical_section(&print_postscript_dialog_lock, "maps.c:Print_postscript_destroy_shell" ); } static void Print_properties_destroy_shell(Widget UNUSED(widget), XtPointer clientData, XtPointer UNUSED(callData) ) { Widget shell = (Widget) clientData; if (!shell) { return; } XtPopdown(shell); begin_critical_section(&print_properties_dialog_lock, "maps.c:Print_properties_destroy_shell" ); XtDestroyWidget(shell); print_properties_dialog = (Widget)NULL; end_critical_section(&print_properties_dialog_lock, "maps.c:Print_properties_destroy_shell" ); } // Print_window: Prints the drawing area to a Postscript file and // then sends it to the printer program (usually "lpr). // static void Print_window( Widget widget, XtPointer UNUSED(clientData), XtPointer UNUSED(callData) ) { #ifdef NO_XPM // fprintf(stderr,"XPM or ImageMagick support not compiled into Xastir!\n"); popup_message_always(langcode("POPEM00035"), "XPM or ImageMagick support not compiled into Xastir! Cannot Print!"); #else // NO_XPM char xpm_filename[MAX_FILENAME]; char ps_filename[MAX_FILENAME]; char command[MAX_FILENAME*2]; char temp[MAX_FILENAME]; int xpmretval; char temp_base_dir[MAX_VALUE]; get_user_base_dir("tmp", temp_base_dir, sizeof(temp_base_dir)); xastir_snprintf(xpm_filename, sizeof(xpm_filename), "%s/print.xpm", temp_base_dir); xastir_snprintf(ps_filename, sizeof(ps_filename), "%s/print.ps", temp_base_dir); busy_cursor(appshell); // Show a busy cursor while we're doing all of this // Get rid of the Print dialog Print_postscript_destroy_shell(widget, print_postscript_dialog, NULL ); if ( debug_level & 512 ) { fprintf(stderr,"Creating %s\n", xpm_filename ); } xastir_snprintf(temp, sizeof(temp), "%s", langcode("PRINT0012") ); statusline(temp,1); // Dumping image to file... if (chdir(temp_base_dir) != 0) { fprintf(stderr,"Couldn't chdir to %s directory for print_window\n", temp_base_dir); return; } xpmretval=XpmWriteFileFromPixmap(XtDisplay(appshell),// Display *display "print.xpm", // char *filename pixmap_final, // Pixmap pixmap (Pixmap)NULL, // Pixmap shapemask NULL ); if (xpmretval != XpmSuccess) { fprintf(stderr,"ERROR writing %s: %s\n", xpm_filename, XpmGetErrorString(xpmretval)); popup_message_always(langcode("POPEM00035"), "Error writing xpm image file! Cannot Print!"); return; } else // We now have the xpm file created on disk { chmod( xpm_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); if ( debug_level & 512 ) { fprintf(stderr,"Convert %s ==> %s\n", xpm_filename, ps_filename ); } // Convert it to a postscript file for printing. This depends // on the ImageMagick command "convert". // if (debug_level & 512) { fprintf(stderr,"Width: %ld\tHeight: %ld\n", screen_width, screen_height); } xastir_snprintf(temp, sizeof(temp), "%s", langcode("PRINT0013") ); statusline(temp,1); // Converting to Postscript... #ifdef HAVE_CONVERT strcpy(command, CONVERT_PATH); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " -filter Point "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, xpm_filename); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, ps_filename); command[sizeof(command)-1] = '\0'; // Terminate string if ( debug_level & 512 ) { fprintf(stderr,"%s\n", command ); } if ( system( command ) != 0 ) { // fprintf(stderr,"\n\nPrint: Couldn't convert from XPM to PS!\n\n\n"); popup_message_always(langcode("POPEM00035"), "Couldn't convert from XPM to PS!"); return; } #endif // HAVE_CONVERT chmod( ps_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); // Delete temporary xpm file if ( !(debug_level & 512) ) { unlink( xpm_filename ); } if ( debug_level & 512 ) { fprintf(stderr,"Printing postscript file %s\n", ps_filename); } // Note: This needs to be changed to "lp" for Solaris. // Also need to have a field to configure the printer name. One // fill-in field could do both. // // Since we could be running SUID root, we don't want to be // calling "system" anyway. Several problems with it. strcpy(command, printer_program); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, ps_filename); command[sizeof(command)-1] = '\0'; // Terminate string if ( debug_level & 512 ) { fprintf(stderr,"%s\n", command); } if (printer_program[0] == '\0') { // fprintf(stderr,"\n\nPrint: No print program defined!\n\n\n"); popup_message_always(langcode("POPEM00035"), "No print program defined!"); return; } if ( system( command ) != 0 ) { // fprintf(stderr,"\n\nPrint: Couldn't send to the printer!\n\n\n"); popup_message_always(langcode("POPEM00035"), "Couldn't send to the printer!"); return; } /* if ( !(debug_level & 512) ) unlink( ps_filename ); */ if ( debug_level & 512 ) { fprintf(stderr," Done printing.\n"); } } xastir_snprintf(temp, sizeof(temp), "%s", langcode("PRINT0014") ); statusline(temp,1); // Finished creating print file. //popup_message( langcode("PRINT0015"), langcode("PRINT0014") ); #endif // NO_XPM } // Print_preview: Prints the drawing area to a Postscript file. If // previewer_program has "gv" in it, then use the various options // selected by the user. If not, skip those options. // static void Print_preview( Widget widget, XtPointer UNUSED(clientData), XtPointer UNUSED(callData) ) { #ifdef NO_XPM // fprintf(stderr,"XPM or ImageMagick support not compiled into Xastir!\n"); popup_message_always(langcode("POPEM00035"), "XPM or ImageMagick support not compiled into Xastir! Cannot Print!"); #else // NO_GRAPHICS char xpm_filename[MAX_FILENAME]; char ps_filename[MAX_FILENAME]; char mono[50] = ""; char invert[50] = ""; char rotate[50] = ""; char scale[50] = ""; char density[50] = ""; char command[MAX_FILENAME*2]; char temp[MAX_FILENAME]; char format[100] = " "; int xpmretval; char temp_base_dir[MAX_VALUE]; get_user_base_dir("tmp", temp_base_dir, sizeof(temp_base_dir)); xastir_snprintf(xpm_filename, sizeof(xpm_filename), "%s/print.xpm", temp_base_dir); xastir_snprintf(ps_filename, sizeof(ps_filename), "%s/print.ps", temp_base_dir); busy_cursor(appshell); // Show a busy cursor while we're doing all of this // Get rid of the Print Properties dialog if it exists Print_properties_destroy_shell(widget, print_properties_dialog, NULL ); if ( debug_level & 512 ) { fprintf(stderr,"Creating %s\n", xpm_filename ); } xastir_snprintf(temp, sizeof(temp), "%s", langcode("PRINT0012") ); statusline(temp,1); // Dumping image to file... if (chdir(temp_base_dir) != 0) { fprintf(stderr,"Couldn't chdir to %s directory for print_preview\n", temp_base_dir); return; } xpmretval=XpmWriteFileFromPixmap(XtDisplay(appshell),// Display *display "print.xpm", // char *filename pixmap_final, // Pixmap pixmap (Pixmap)NULL, // Pixmap shapemask NULL ); if (xpmretval != XpmSuccess) { fprintf(stderr,"ERROR writing %s: %s\n", xpm_filename, XpmGetErrorString(xpmretval)); popup_message_always(langcode("POPEM00035"), "Error writing XPM file!"); return; } else // We now have the xpm file created on disk { chmod( xpm_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); if ( debug_level & 512 ) { fprintf(stderr,"Convert %s ==> %s\n", xpm_filename, ps_filename ); } // If we're not using "gv", skip most of the code below and // go straight to the previewer program portion of the code. // if ( strstr(previewer_program,"gv") ) { // Convert it to a postscript file for printing. This // depends on the ImageMagick command "convert". // // Other options to try in the future: // -label // if ( print_auto_scale ) { // sprintf(scale, "-geometry 612x792 -page 612x792 "); // "Letter" size at 72 dpi // sprintf(scale, "-sample 612x792 -page 612x792 "); // "Letter" size at 72 dpi xastir_snprintf(scale, sizeof(scale), "-page 1275x1650+0+0 "); // "Letter" size at 150 dpi } else { scale[0] = '\0'; // Empty string } if ( print_in_monochrome ) { xastir_snprintf(mono, sizeof(mono), "-monochrome +dither " ); // Monochrome } else { xastir_snprintf(mono, sizeof(mono), "+dither "); // Color } if ( print_invert ) { xastir_snprintf(invert, sizeof(invert), "-negate " ); // Reverse Colors } else { invert[0] = '\0'; // Empty string } if (debug_level & 512) { fprintf(stderr,"Width: %ld\tHeight: %ld\n", screen_width, screen_height); } if ( print_rotated ) { xastir_snprintf(rotate, sizeof(rotate), "-rotate -90 " ); #ifdef HAVE_OLD_GV xastir_snprintf(format, sizeof(format), "-landscape " ); #else // HAVE_OLD_GV xastir_snprintf(format, sizeof(format), "--orientation=landscape " ); #endif // HAVE_OLD_GV } else if ( print_auto_rotation ) { // Check whether the width or the height of the // pixmap is greater. If width is greater than // height, rotate the image by 270 degrees. if (screen_width > screen_height) { xastir_snprintf(rotate, sizeof(rotate), "-rotate -90 " ); #ifdef HAVE_OLD_GV xastir_snprintf(format, sizeof(format), "-landscape " ); #else // HAVE_OLD_GV xastir_snprintf(format, sizeof(format), "--orientation=landscape " ); #endif // HAVE_OLD_GV if (debug_level & 512) { fprintf(stderr,"Rotating\n"); } } else { rotate[0] = '\0'; // Empty string if (debug_level & 512) { fprintf(stderr,"Not Rotating\n"); } } } else { rotate[0] = '\0'; // Empty string if (debug_level & 512) { fprintf(stderr,"Not Rotating\n"); } } // Higher print densities require more memory and time // to process xastir_snprintf(density, sizeof(density), "-density %dx%d", print_resolution, print_resolution ); xastir_snprintf(temp, sizeof(temp), "%s", langcode("PRINT0013") ); statusline(temp,1); // Converting to Postscript... // Filters: // Point (ok at higher dpi's) // Box (not too bad) // Triangle (no) // Hermite (no) // Hanning (no) // Hamming (no) // Blackman (better but still not good) // Gaussian (no) // Quadratic (no) // Cubic (no) // Catrom (not too bad) // Mitchell (no) // Lanczos (no) // Bessel (no) // Sinc (not too bad) } #ifdef HAVE_CONVERT strcpy(command, CONVERT_PATH); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " -filter Point "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, mono); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, invert); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, rotate); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, scale); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, density); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, xpm_filename); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, ps_filename); command[sizeof(command)-1] = '\0'; // Terminate string if ( debug_level & 512 ) { fprintf(stderr,"%s\n", command ); } if ( system( command ) != 0 ) { // fprintf(stderr,"\n\nPrint: Couldn't convert from XPM to PS!\n\n\n"); popup_message_always(langcode("POPEM00035"), "Couldn't convert from XPM to PS!"); return; } #endif // HAVE_CONVERT chmod( ps_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); // Delete temporary xpm file if ( !(debug_level & 512) ) { unlink( xpm_filename ); } if ( debug_level & 512 ) { fprintf(stderr,"Printing postscript file %s\n", ps_filename); } // Since we could be running SUID root, we don't want to be // calling "system" anyway. Several problems with it. // Bring up the postscript viewer strcpy(command, previewer_program); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, format); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, ps_filename); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " &"); command[sizeof(command)-1] = '\0'; // Terminate string if ( debug_level & 512 ) { fprintf(stderr,"%s\n", command); } if (previewer_program[0] == '\0') { // fprintf(stderr,"\n\nPrint: No print previewer defined!\n\n\n"); popup_message_always(langcode("POPEM00035"), "No print previewer defined!"); return; } if ( system( command ) != 0 ) { // fprintf(stderr,"\n\nPrint: Couldn't bring up the postscript viewer!\n\n\n"); popup_message_always(langcode("POPEM00035"), "Couldn't bring up the viewer!"); return; } /* if ( !(debug_level & 512) ) unlink( ps_filename ); */ if ( debug_level & 512 ) { fprintf(stderr," Done printing.\n"); } } xastir_snprintf(temp, sizeof(temp), "%s", langcode("PRINT0014") ); statusline(temp,1); // Finished creating print file. //popup_message( langcode("PRINT0015"), langcode("PRINT0014") ); #endif // NO_XPM } /* * Auto_rotate * */ static void Auto_rotate( Widget UNUSED(widget), XtPointer clientData, XtPointer callData) { char *which = (char *)clientData; XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData; if(state->set) { print_auto_rotation = atoi(which); print_rotated = 0; XmToggleButtonSetState(rotate_90, FALSE, FALSE); } else { print_auto_rotation = 0; } } /* * Rotate_90 * */ static void Rotate_90( Widget UNUSED(widget), XtPointer clientData, XtPointer callData) { char *which = (char *)clientData; XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData; if(state->set) { print_rotated = atoi(which); print_auto_rotation = 0; XmToggleButtonSetState(auto_rotate, FALSE, FALSE); } else { print_rotated = 0; } } /* * Auto_scale * */ static void Auto_scale( Widget UNUSED(widget), XtPointer clientData, XtPointer callData) { char *which = (char *)clientData; XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData; if(state->set) { print_auto_scale = atoi(which); } else { print_auto_scale = 0; } } /* * Monochrome * */ void Monochrome( Widget UNUSED(widget), XtPointer clientData, XtPointer callData) { char *which = (char *)clientData; XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData; if(state->set) { print_in_monochrome = atoi(which); } else { print_in_monochrome = 0; } } /* * Invert * */ static void Invert( Widget UNUSED(widget), XtPointer clientData, XtPointer callData) { char *which = (char *)clientData; XmToggleButtonCallbackStruct *state = (XmToggleButtonCallbackStruct *)callData; if(state->set) { print_invert = atoi(which); } else { print_invert = 0; } } // Print_properties: Prints the drawing area to a PostScript file. // Provides various togglebuttons for configuring the "gv" previewer // only. // // Perhaps later: // 1) Select an area on the screen to print // 2) -label // void Print_properties( Widget w, XtPointer UNUSED(clientData), XtPointer UNUSED(callData) ) { static Widget pane, scrollwindow, form, button_ok, button_cancel, sep, auto_scale, // paper_size, paper_size_data, scale, scale_data, blank_background, // res_label1, res_label2, res_x, res_y, monochrome, invert; Atom delw; // Get rid of the Print dialog Print_postscript_destroy_shell(w, print_postscript_dialog, NULL ); // If we're not using "gv", skip the entire dialog below and go // straight to the actual previewer function. // if ( !strstr(previewer_program,"gv") ) { Print_preview(w, NULL, NULL); return; } if (!print_properties_dialog) { begin_critical_section(&print_properties_dialog_lock, "maps.c:Print_properties" ); print_properties_dialog = XtVaCreatePopupShell(langcode("PRINT0001"), xmDialogShellWidgetClass, appshell, XmNdeleteResponse, XmDESTROY, XmNdefaultPosition, FALSE, XmNfontList, fontlist1, NULL); pane = XtVaCreateWidget("Print_properties pane",xmPanedWindowWidgetClass, print_properties_dialog, XmNbackground, colors[0xff], NULL); scrollwindow = XtVaCreateManagedWidget("scrollwindow", xmScrolledWindowWidgetClass, pane, XmNscrollingPolicy, XmAUTOMATIC, NULL); form = XtVaCreateWidget("Print_properties form", xmFormWidgetClass, scrollwindow, XmNfractionBase, 2, XmNbackground, colors[0xff], XmNautoUnmanage, FALSE, XmNshadowThickness, 1, NULL); /* paper_size = XtVaCreateManagedWidget(langcode("PRINT0002"),xmLabelWidgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNfontList, fontlist1, NULL); XtSetSensitive(paper_size,FALSE); paper_size_data = XtVaCreateManagedWidget("Print_properties paper_size_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 15, XmNwidth, ((15*7)+2), XmNmaxLength, 15, XmNbackground, colors[0x0f], XmNtopAttachment,XmATTACH_FORM, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, paper_size, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_FORM, XmNrightOffset, 10, XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtSetSensitive(paper_size_data,FALSE); */ auto_rotate = XtVaCreateManagedWidget(langcode("PRINT0003"),xmToggleButtonWidgetClass,form, // XmNtopAttachment, XmATTACH_WIDGET, // XmNtopWidget, paper_size_data, // XmNtopOffset, 5, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset,10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(auto_rotate,XmNvalueChangedCallback,Auto_rotate,"1"); rotate_90 = XtVaCreateManagedWidget(langcode("PRINT0004"),xmToggleButtonWidgetClass,form, // XmNtopAttachment, XmATTACH_WIDGET, // XmNtopWidget, paper_size_data, // XmNtopOffset, 5, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, auto_rotate, XmNleftOffset,10, XmNrightAttachment, XmATTACH_FORM, XmNrightOffset, 10, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(rotate_90,XmNvalueChangedCallback,Rotate_90,"1"); auto_scale = XtVaCreateManagedWidget(langcode("PRINT0005"),xmToggleButtonWidgetClass,form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, auto_rotate, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset,10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(auto_scale,XmNvalueChangedCallback,Auto_scale,"1"); /* scale = XtVaCreateManagedWidget(langcode("PRINT0006"),xmLabelWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, auto_rotate, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, auto_scale, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNfontList, fontlist1, NULL); XtSetSensitive(scale,FALSE); scale_data = XtVaCreateManagedWidget("Print_properties scale_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 15, XmNwidth, ((15*7)+2), XmNmaxLength, 15, XmNbackground, colors[0x0f], XmNtopAttachment,XmATTACH_WIDGET, XmNtopWidget, auto_rotate, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, scale, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_FORM, XmNrightOffset, 10, XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtSetSensitive(scale_data,FALSE); */ /* blank_background = XtVaCreateManagedWidget(langcode("PRINT0007"),xmToggleButtonWidgetClass,form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, scale_data, XmNtopWidget, auto_rotate, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset ,10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtSetSensitive(blank_background,FALSE); */ monochrome = XtVaCreateManagedWidget(langcode("PRINT0008"),xmToggleButtonWidgetClass,form, XmNtopAttachment, XmATTACH_WIDGET, // XmNtopWidget, blank_background, XmNtopWidget, auto_scale, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset,10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(monochrome,XmNvalueChangedCallback,Monochrome,"1"); invert = XtVaCreateManagedWidget(langcode("PRINT0016"),xmToggleButtonWidgetClass,form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, monochrome, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset,10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(invert,XmNvalueChangedCallback,Invert,"1"); /* res_label1 = XtVaCreateManagedWidget(langcode("PRINT0009"),xmLabelWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, invert, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNfontList, fontlist1, NULL); XtSetSensitive(res_label1,FALSE); res_x = XtVaCreateManagedWidget("Print_properties resx_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 15, XmNwidth, ((15*7)+2), XmNmaxLength, 15, XmNbackground, colors[0x0f], XmNtopAttachment,XmATTACH_WIDGET, XmNtopWidget, invert, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, res_label1, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_NONE, XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtSetSensitive(res_x,FALSE); res_label2 = XtVaCreateManagedWidget("X",xmLabelWidgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, invert, XmNtopOffset, 10, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, res_x, XmNleftOffset, 10, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNfontList, fontlist1, NULL); XtSetSensitive(res_label2,FALSE); res_y = XtVaCreateManagedWidget("Print_properties res_y_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 15, XmNwidth, ((15*7)+2), XmNmaxLength, 15, XmNbackground, colors[0x0f], XmNtopAttachment,XmATTACH_WIDGET, XmNtopWidget, invert, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, res_label2, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_FORM, XmNrightOffset, 10, XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtSetSensitive(res_y,FALSE); */ sep = XtVaCreateManagedWidget("Print_properties sep", xmSeparatorGadgetClass,form, XmNorientation, XmHORIZONTAL, XmNtopAttachment,XmATTACH_WIDGET, // XmNtopWidget, res_y, XmNtopWidget, invert, XmNtopOffset, 10, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment,XmATTACH_FORM, XmNbackground, colors[0xff], XmNfontList, fontlist1, NULL); // button_ok = XtVaCreateManagedWidget(langcode("PRINT0011"),xmPushButtonGadgetClass, form, button_ok = XtVaCreateManagedWidget(langcode("PRINT0010"),xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, sep, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_FORM, XmNbottomOffset, 5, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 0, XmNleftOffset, 3, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 1, XmNrightOffset, 2, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); button_cancel = XtVaCreateManagedWidget(langcode("UNIOP00002"),xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, sep, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_FORM, XmNbottomOffset, 5, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 1, XmNleftOffset, 3, XmNrightAttachment, XmATTACH_POSITION, XmNrightPosition, 2, XmNrightOffset, 5, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(button_ok, XmNactivateCallback, Print_preview, NULL ); XtAddCallback(button_cancel, XmNactivateCallback, Print_properties_destroy_shell, print_properties_dialog); XmToggleButtonSetState(rotate_90,FALSE,FALSE); XmToggleButtonSetState(auto_rotate,TRUE,FALSE); if (print_auto_rotation) { XmToggleButtonSetState(auto_rotate, TRUE, TRUE); } else { XmToggleButtonSetState(auto_rotate, FALSE, TRUE); } if (print_rotated) { XmToggleButtonSetState(rotate_90, TRUE, TRUE); } else { XmToggleButtonSetState(rotate_90, FALSE, TRUE); } if (print_in_monochrome) { XmToggleButtonSetState(monochrome, TRUE, FALSE); } else { XmToggleButtonSetState(monochrome, FALSE, FALSE); } if (print_invert) { XmToggleButtonSetState(invert, TRUE, FALSE); } else { XmToggleButtonSetState(invert, FALSE, FALSE); } if (print_auto_scale) { XmToggleButtonSetState(auto_scale, TRUE, TRUE); } else { XmToggleButtonSetState(auto_scale, FALSE, TRUE); } // XmTextFieldSetString(paper_size_data,print_paper_size); end_critical_section(&print_properties_dialog_lock, "maps.c:Print_properties" ); pos_dialog(print_properties_dialog); delw = XmInternAtom(XtDisplay(print_properties_dialog),"WM_DELETE_WINDOW", FALSE); XmAddWMProtocolCallback(print_properties_dialog, delw, Print_properties_destroy_shell, (XtPointer)print_properties_dialog); XtManageChild(form); XtManageChild(pane); resize_dialog(form, print_properties_dialog); XtPopup(print_properties_dialog,XtGrabNone); // Move focus to the Cancel button. This appears to highlight the // button fine, but we're not able to hit the key to // have that default function happen. Note: We _can_ hit the // key, and that activates the option. // XmUpdateDisplay(print_properties_dialog); XmProcessTraversal(button_cancel, XmTRAVERSE_CURRENT); } else { (void)XRaiseWindow(XtDisplay(print_properties_dialog), XtWindow(print_properties_dialog)); } } // General print dialog. From here we can either print Postscript // files to the device selected in this dialog, or head off to a // print preview program that might allow us a variety of print // options. From here we should be able to set the print device // and the print preview program & path. // void Print_Postscript( Widget UNUSED(w), XtPointer UNUSED(clientData), XtPointer UNUSED(callData) ) { static Widget pane, scrollwindow, form, button_print, button_cancel, sep, button_preview; Atom delw; if (!print_postscript_dialog) { begin_critical_section(&print_postscript_dialog_lock, "maps.c:Print_Postscript" ); print_postscript_dialog = XtVaCreatePopupShell(langcode("PULDNFI015"), xmDialogShellWidgetClass, appshell, XmNdeleteResponse, XmDESTROY, XmNdefaultPosition, FALSE, XmNfontList, fontlist1, NULL); pane = XtVaCreateWidget("Print_postscript pane",xmPanedWindowWidgetClass, print_postscript_dialog, XmNbackground, colors[0xff], NULL); scrollwindow = XtVaCreateManagedWidget("scrollwindow", xmScrolledWindowWidgetClass, pane, XmNscrollingPolicy, XmAUTOMATIC, NULL); form = XtVaCreateWidget("Print_postscript form", xmFormWidgetClass, scrollwindow, XmNfractionBase, 3, XmNbackground, colors[0xff], XmNautoUnmanage, FALSE, XmNshadowThickness, 1, NULL); // "Direct to:" button_print = XtVaCreateManagedWidget(langcode("PRINT1001"),xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_FORM, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 5, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); printer_data = XtVaCreateManagedWidget("Print_Postscript printer_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 40, XmNwidth, ((40*7)+2), XmNmaxLength, MAX_FILENAME, XmNbackground, colors[0x0f], XmNtopAttachment,XmATTACH_FORM, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, button_print, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_FORM, XmNrightOffset, 5, XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); // "Via Previewer:" button_preview = XtVaCreateManagedWidget(langcode("PRINT1002"),xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, button_print, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 5, XmNrightAttachment, XmATTACH_NONE, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); previewer_data = XtVaCreateManagedWidget("Print_Postscript previewer_data", xmTextFieldWidgetClass, form, XmNeditable, TRUE, XmNcursorPositionVisible, TRUE, XmNsensitive, TRUE, XmNshadowThickness, 1, XmNcolumns, 40, XmNwidth, ((40*7)+2), XmNmaxLength, MAX_FILENAME, XmNbackground, colors[0x0f], XmNtopAttachment,XmATTACH_WIDGET, XmNtopWidget, button_print, XmNtopOffset, 5, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, button_preview, XmNleftOffset, 10, XmNrightAttachment,XmATTACH_FORM, XmNrightOffset, 5, XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); sep = XtVaCreateManagedWidget("Print_postscript sep", xmSeparatorGadgetClass,form, XmNorientation, XmHORIZONTAL, XmNtopAttachment,XmATTACH_WIDGET, XmNtopWidget, button_preview, XmNtopOffset, 10, XmNbottomAttachment,XmATTACH_NONE, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment,XmATTACH_FORM, XmNbackground, colors[0xff], XmNfontList, fontlist1, NULL); button_cancel = XtVaCreateManagedWidget(langcode("UNIOP00002"),xmPushButtonGadgetClass, form, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, sep, XmNtopOffset, 5, XmNbottomAttachment, XmATTACH_FORM, XmNbottomOffset, 5, XmNleftAttachment, XmATTACH_FORM, XmNleftOffset, 5, XmNrightAttachment, XmATTACH_FORM, XmNrightOffset, 5, XmNbackground, colors[0xff], XmNnavigationType, XmTAB_GROUP, XmNtraversalOn, TRUE, XmNfontList, fontlist1, NULL); XtAddCallback(button_preview, XmNactivateCallback, Print_properties, NULL ); XtAddCallback(button_print, XmNactivateCallback, Print_window, NULL ); XtAddCallback(button_cancel, XmNactivateCallback, Print_postscript_destroy_shell, print_postscript_dialog); // Fill in the text fields from persistent variables out of the config file. XmTextFieldSetString(printer_data, printer_program); XmTextFieldSetString(previewer_data, previewer_program); end_critical_section(&print_postscript_dialog_lock, "maps.c:Print_Postscript" ); pos_dialog(print_postscript_dialog); delw = XmInternAtom(XtDisplay(print_postscript_dialog),"WM_DELETE_WINDOW", FALSE); XmAddWMProtocolCallback(print_postscript_dialog, delw, Print_postscript_destroy_shell, (XtPointer)print_postscript_dialog); XtManageChild(form); XtManageChild(pane); resize_dialog(form, print_postscript_dialog); XtPopup(print_postscript_dialog,XtGrabNone); // Move focus to the Cancel button. This appears to highlight the // button fine, but we're not able to hit the key to // have that default function happen. Note: We _can_ hit the // key, and that activates the option. // XmUpdateDisplay(print_postscript_dialog); XmProcessTraversal(button_cancel, XmTRAVERSE_CURRENT); } else { (void)XRaiseWindow(XtDisplay(print_postscript_dialog), XtWindow(print_postscript_dialog)); } } // Create png image (for use in web browsers??). Requires that "convert" // from the ImageMagick package be installed on the system. At the // point this thread is started, the XPM file has already been // created. We now create a .geo file to go with the .png file. // #ifndef NO_XPM static void* snapshot_thread(void * UNUSED(arg) ) { char xpm_filename[MAX_FILENAME]; char png_filename[MAX_FILENAME]; char geo_filename[MAX_FILENAME]; char kml_filename[MAX_FILENAME]; // filename for kml file that describes the png file in keyhole markup language char timestring[101]; // string representation of the time heard or the current time FILE *f; FILE *fk; // file handle for kml file time_t expire_time; #ifdef HAVE_CONVERT char command[MAX_FILENAME*2]; #endif // HAVE_CONVERT char temp_base_dir[MAX_VALUE]; get_user_base_dir("tmp", temp_base_dir, sizeof(temp_base_dir)); // The pthread_detach() call means we don't care about the // return code and won't use pthread_join() later. Makes // threading more efficient. (void)pthread_detach(pthread_self()); xastir_snprintf(xpm_filename, sizeof(xpm_filename), "%s/snapshot.xpm", temp_base_dir); xastir_snprintf(png_filename, sizeof(png_filename), "%s/snapshot.png", temp_base_dir); // Same for the .geo filename xastir_snprintf(geo_filename, sizeof(geo_filename), "%s/snapshot.geo", temp_base_dir); // Same for the .kml filename xastir_snprintf(kml_filename, sizeof(kml_filename), "%s/snapshot.kml", temp_base_dir); // Create a .geo file to match the new png image // Likewise for a matching .kml file f = fopen(geo_filename,"w"); // Overwrite whatever file // is there. fk = fopen(kml_filename,"w"); if (f == NULL || fk == NULL) { if (f==NULL) { fprintf(stderr,"Couldn't open %s\n",geo_filename); } if (fk==NULL) { fprintf(stderr,"Couldn't open %s\n",kml_filename); } } else { float lat1, long1, lat2, long2; long1 = f_NW_corner_longitude; lat1 = f_NW_corner_latitude; long2 = f_SE_corner_longitude; lat2 = f_SE_corner_latitude; // FILENAME world1.xpm // # x y lon lat // TIEPOINT 0 0 -180 90 // TIEPOINT 639 319 180 -90 // IMAGESIZE 640 320 // REFRESH 250 fprintf(f,"FILENAME snapshot.png\n"); fprintf(f,"# x y lon lat\n"); fprintf(f,"TIEPOINT 0 0 %8.5f %8.5f\n", long1, lat1); fprintf(f,"TIEPOINT %-4d %-4d %8.5f %8.5f\n", (int)screen_width-1, (int)screen_height-1, long2, lat2); fprintf(f,"IMAGESIZE %-4d %-4d\n", (int)screen_width, (int)screen_height); fprintf(f,"REFRESH 250\n"); fclose(f); // Write a matching kml file that describes the location of the snapshot on // the Earth's surface. // Another kml file pointing to the location of this file with a networklinkcontrol element // and an update element loaded into a kml application should be able to reload this file // at regular intervals. // See kml documentation of: // // // // // // // // http://www.example.com/cgi-bin/screenshot.kml // onExpire // // // // // // TODO: Calculate a suitable range and tilt for viewing the snapshot draped on the // underlying terrain. fprintf(fk,"\n"); fprintf(fk,"\n"); // Add an expire time matching the time when the next snapshot should // be produced, so that a network link with an onExpire refresh mode // will check for the next snapshot. expire_time = sec_now() + (time_t)(snapshot_interval * 60); if (get_w3cdtf_datetime(expire_time, timestring, False, False)) { if (strlen(timestring) > 0) { fprintf(fk," \n"); fprintf(fk," %s\n",timestring); fprintf(fk," \n"); } } fprintf(fk," \n"); fprintf(fk," XASTIR Snapshot from %s\n",my_callsign); fprintf(fk," 1\n"); fprintf(fk," \n"); fprintf(fk," Xastir snapshot\n"); fprintf(fk," 1\n"); // timestamp the overlay with the current time if (get_w3cdtf_datetime(sec_now(), timestring, True, True)) { if (strlen(timestring) > 0) { fprintf(fk," %s\n",timestring); fprintf(fk," Overlay shows screen visible for %s in Xastir at %s.\n",my_callsign,timestring); } } else { fprintf(fk," Overlay shows screen visible for %s in Xastir.\n",my_callsign); } fprintf(fk," \n"); fprintf(fk," %8.5f\n",f_center_longitude); fprintf(fk," %8.5f\n",f_center_latitude); fprintf(fk," 0\n"); fprintf(fk," 30350.36838438907\n"); // range in meters from viewer to lookat point fprintf(fk," 0\n"); // 0 is looking straight down fprintf(fk," clampToGround\n"); fprintf(fk," 0\n"); // 0 is north at top, 90 east at top fprintf(fk," \n"); fprintf(fk," \n"); fprintf(fk," snapshot.png\n"); fprintf(fk," \n"); fprintf(fk," \n"); fprintf(fk," %8.5f\n",lat1); fprintf(fk," %8.5f\n",lat2); fprintf(fk," %8.5f\n",long2); fprintf(fk," %8.5f\n",long1); fprintf(fk," 0\n"); fprintf(fk," \n"); fprintf(fk," \n"); fprintf(fk," \n"); fprintf(fk,"\n"); fclose(fk); chmod( geo_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); chmod( kml_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); } if ( debug_level & 512 ) { fprintf(stderr,"Convert %s ==> %s\n", xpm_filename, png_filename ); } #ifdef HAVE_CONVERT // Convert it to a png file. This depends upon having the // ImageMagick command "convert" installed. strcpy(command, CONVERT_PATH); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " -quality 100 -colors 256 "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, xpm_filename); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, " "); command[sizeof(command)-1] = '\0'; // Terminate string strcat(command, png_filename); command[sizeof(command)-1] = '\0'; // Terminate string if ( system( command ) != 0 ) { // We _may_ have had an error. Check errno to make // sure. if (errno) { fprintf(stderr, "%s\n", strerror(errno)); fprintf(stderr, "Failed to convert snapshot: %s -> %s\n", xpm_filename, png_filename); } else { fprintf(stderr, "System call return error: convert: %s -> %s\n", xpm_filename, png_filename); } } else { chmod( png_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); // // Delete temporary xpm file // unlink( xpm_filename ); if ( debug_level & 512 ) { fprintf(stderr," Done creating png.\n"); } } #endif // HAVE_CONVERT // Signify that we're all done and that another snapshot can // occur. doing_snapshot = 0; return(NULL); } #endif // NO_XPM // Starts a separate thread that creates a png image from the // current displayed image. // void Snapshot(void) { #ifndef NO_XPM pthread_t snapshot_thread_id; char xpm_filename[MAX_FILENAME]; int xpmretval; #endif // NO_XPM char temp_base_dir[MAX_VALUE]; get_user_base_dir("tmp", temp_base_dir, sizeof(temp_base_dir)); // Check whether we're already doing a snapshot if (doing_snapshot) { return; } // Time to take another snapshot? // New snapshot interval based on slider in Configure Timing // dialog (in minutes) if (sec_now() < (last_snapshot + (snapshot_interval * 60)) ) { return; } last_snapshot = sec_now(); // Set up timer for next time #ifndef NO_XPM if (debug_level & 512) { fprintf(stderr,"Taking Snapshot\n"); } doing_snapshot++; // Set up the XPM filename that we'll use xastir_snprintf(xpm_filename, sizeof(xpm_filename), "%s/snapshot.xpm", temp_base_dir); if ( debug_level & 512 ) { fprintf(stderr,"Creating %s\n", xpm_filename ); } // Create an XPM file from pixmap_final. if (chdir(temp_base_dir) != 0) { fprintf(stderr,"Couldn't chdir to %s directory for snapshot\n", temp_base_dir); return; } xpmretval=XpmWriteFileFromPixmap(XtDisplay(appshell), // Display *display "snapshot.xpm", // char *filename pixmap_final, // Pixmap pixmap (Pixmap)NULL, // Pixmap shapemask NULL ); if (xpmretval != XpmSuccess) { fprintf(stderr,"ERROR writing %s: %s\n", xpm_filename, XpmGetErrorString(xpmretval)); return; } chmod( xpm_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH ); //----- Start New Thread ----- // // Here we start a new thread. We'll communicate with the main // thread via global variables. Use mutex locks if there might // be a conflict as to when/how we're updating those variables. // if (pthread_create(&snapshot_thread_id, NULL, snapshot_thread, NULL)) { fprintf(stderr,"Error creating snapshot thread\n"); } else { // We're off and running with the new thread! } #endif // NO_XPM } // Function to remove double-quote characters and spaces that occur // outside of the double-quote characters. void clean_string(char *input) { char *i; char *j; //fprintf(stderr,"|%s|\t",input); // Remove any double quote characters i = index(input,'"'); // Find first quote character, if any if (i != NULL) { j = index(i+1,'"'); // Find second quote character, if any if (j != NULL) // Found two quote characters { j[0] = '\0'; // Terminate the string at the 2nd quote // Can't use strcpy here because it can't work with // overlapping strings. strcpy is a dangerous function // anyway and shouldn't be used. memmove(input, i+1, j-i); } else // We only found one quote character. What to do? { // fprintf(stderr,"clean_string: Only one quote found!\n"); } } //fprintf(stderr,"|%s|\n",input); // Remove leading/trailing spaces? } // Test map visibility (on screen) // // Input parameters are in Xastir coordinate system (fastest for us) // check_percentage: // 0 = don't check // 1 = check map size versus viewport scale. Return 0 if map // is too large/small (percentage-wise) to be displayed. // // Returns: MAP_NOT_VIS if map is _not_ visible // MAP_IS_VIS if map _is_ visible // // Xastir Coordinate System: // // 0 (90 deg. or 90N) // // 0 (-180 deg. or 180W) 129,600,000 (180 deg. or 180E) // // 64,800,000 (-90 deg. or 90S) // // Note that we already have map_visible() and map_visible_lat_lon() // routines. // enum map_onscreen_enum map_onscreen(long left, long right, long top, long bottom, int UNUSED(check_percentage) ) { enum map_onscreen_enum in_window = MAP_NOT_VIS; if (map_visible((unsigned long)bottom, (unsigned long)top, (unsigned long)left, (unsigned long)right)) { in_window = MAP_IS_VIS; //fprintf(stderr,"map_onscreen:Map is visible\n"); } #ifdef MAP_SCALE_CHECK // Check whether map is too large/small for our current scale? // Check whether the map extents are < XX% of viewscreen size // (both directions), or viewscreen is > XX% of map extents // (either direction). This will knock out maps that are too // large/small to be displayed at this zoom level. //WE7U if (in_window && check_percentage) { long map_x, map_y, view_x, view_y; float percentage = 0.04; map_x = right - left; if (map_x < 0) { map_x = 0; } map_y = bottom - top; if (map_y < 0) { map_y = 0; } view_x = max_NW_corner_longitude - NW_corner_longitude; if (view_x < 0) { view_x = 0; } view_y = max_NW_corner_latitude - NW_corner_latitude; if (view_y < 0) { view_y = 0; } //fprintf(stderr,"\n map_x: %d\n", map_x); //fprintf(stderr," map_y: %d\n", map_y); //fprintf(stderr,"view_x: %d\n", view_x); //fprintf(stderr,"view_y: %d\n", view_y); if ((map_x < (view_x * percentage )) && (map_y < (view_x * percentage))) { in_window = 0; // Send back "not-visible" flag fprintf(stderr,"map too small for view: %d%%\n",(int)(percentage * 100)); } // if ((view_x < (map_x * percentage)) && (view_y < (map_x * percentage))) { // in_window = 0; // Send back "not-visible" flag //fprintf(stderr,"view too small for map: %d%%\n",(int)(percentage * 100)); // } } #endif // MAP_SCALE_CHECK //fprintf(stderr,"map_onscreen returning %d\n", in_window); return (in_window); } // Function which checks whether a map is onscreen, but does so by // finding the map boundaries from the map index. The only input // parameter is the complete path/filename. // // Returns: MAP_NOT_VIS if map is _not_ visible // MAP_IS_VIS if map _is_ visible // MAP_NOT_INDEXED if the map is not in the index // enum map_onscreen_enum map_onscreen_index(char *filename) { unsigned long top, bottom, left, right; enum map_onscreen_enum onscreen = MAP_NOT_INDEXED; int max_zoom, min_zoom; int map_layer, draw_filled, usgs_drg, auto_maps; // Unused in this function if (index_retrieve(filename, &bottom, &top, &left, &right, &max_zoom, &min_zoom, &map_layer, &draw_filled, &usgs_drg, &auto_maps) ) { //fprintf(stderr, "Map found in index: %s\n", filename); // Map was in the index, check for visibility and scale // Check whether the map extents are < XX% of viewscreen // size (both directions), or viewscreen is > XX% of map // extents (either direction). This will knock out maps // that are too large/small to be displayed at this zoom // level. if (map_onscreen(left, right, top, bottom, 1)) { //fprintf(stderr, "Map found in index and onscreen: %s\n", filename); if (((max_zoom == 0) || ((max_zoom != 0) && (scale_y <= max_zoom))) && ((min_zoom == 0) || ((min_zoom != 0) && (scale_y >= min_zoom)))) { onscreen = MAP_IS_VIS; //fprintf(stderr,"Map in the zoom zone: %s\n",filename); } else { onscreen = MAP_NOT_VIS; //fprintf(stderr,"Map not in the zoom zone: %s\n",filename); } // Check whether the map extents are < XX% of viewscreen size (both // directions), or viewscreen is > XX% of map extents (either // direction). This will knock out maps that are too large/small to // be displayed at this zoom level. } else // Map is not visible { onscreen = MAP_NOT_VIS; //fprintf(stderr,"Map found in index but not onscreen: %s\n",filename); } } else // Map is not in the index { onscreen = MAP_NOT_INDEXED; //fprintf(stderr,"Map not found in index: %s\n",filename); } return(onscreen); } /********************************************************** * draw_map() * * Function which tries to figure out what type of map or * image file we're dealing with, and takes care of getting * it onto the screen. Calls other functions to deal with * .geo/.tif/.shp maps. * * If destination_pixmap == DRAW_NOT, then we'll not draw * the map anywhere, but we'll determine the map extents * and write them to the map index file. **********************************************************/ /* table of map drivers, selected by filename extension */ extern void draw_dos_map(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); extern void draw_palm_image_map(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); #ifdef HAVE_LIBSHP extern void draw_shapefile_map (Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); extern void clear_dbfawk_sigs(void); #endif /* HAVE_LIBSHP */ #ifdef HAVE_LIBGEOTIFF extern void draw_geotiff_image_map(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); #endif /* HAVE_LIBGEOTIFF */ extern void draw_geo_image_map(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); extern void draw_gnis_map(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); extern void draw_pop_map(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); struct { char *ext; enum {none=0, map, tif, geo, gnis, shp, pop} type; void (*func)(Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags); } map_driver[] = { {"map",map,draw_dos_map}, #ifdef HAVE_LIBGEOTIFF {"tif",tif,draw_geotiff_image_map}, #endif /* HAVE_LIBGEOTIFF */ {"geo",geo,draw_geo_image_map}, {"gnis",gnis,draw_gnis_map}, {"pop",pop,draw_pop_map}, #ifdef HAVE_LIBSHP {"shp",shp,draw_shapefile_map}, #endif /* HAVE_LIBSHP */ {NULL,none,NULL} }, *map_driver_ptr; void draw_map (Widget w, char *dir, char *filenm, alert_entry *alert, u_char alert_color, int destination_pixmap, map_draw_flags *draw_flags) { enum map_onscreen_enum onscreen; char *ext; char file[MAX_FILENAME]; if ((ext = get_map_ext(filenm)) == NULL) { return; } if (debug_level & 16) { fprintf(stderr,"draw_map: Searching for map driver\n"); } for (map_driver_ptr = map_driver; map_driver_ptr->ext; map_driver_ptr++) { if (strcasecmp(ext,map_driver_ptr->ext) == 0) { if (debug_level & 16) fprintf(stderr, "draw_map: Found map driver: %s: %d\n", ext, map_driver_ptr->type); break; /* found our map_driver */ } } if (map_driver_ptr->type == none) /* fall thru: unknown map driver */ { // Check whether we're indexing or drawing the map if ( (destination_pixmap != INDEX_CHECK_TIMESTAMPS) && (destination_pixmap != INDEX_NO_TIMESTAMPS) ) { // We're drawing, not indexing. Output a warning // message. fprintf(stderr,"*** draw_map: Unknown map type: %s ***\n", filenm); } else // We're indexing { if (debug_level & 16) { fprintf(stderr,"draw_map: No map driver found\n"); } } return; } onscreen = map_onscreen_index(filenm); // Check map index // Check whether we're indexing or drawing the map if ( (destination_pixmap == INDEX_CHECK_TIMESTAMPS) || (destination_pixmap == INDEX_NO_TIMESTAMPS) ) { // We're indexing maps if (onscreen != MAP_NOT_INDEXED) // We already have an index entry for this map. // This is where we pick up a big speed increase: // Refusing to index a map that's already indexed. { return; // Skip it. } } else // We're drawing maps { // See if map is visible. If not, skip it. if (onscreen == MAP_NOT_VIS) // Map is not visible, skip it. { //fprintf(stderr,"map not visible\n"); if (alert) { alert->flags[on_screen] = 'N'; } return; } } xastir_snprintf(file, sizeof(file), "%s/%s", dir, filenm); // Used for debugging. If we get a segfault on a map, this is // often the only way of finding out which map file we can't // handle. if (debug_level & 16) { fprintf(stderr,"draw_map: %s\n",file); } /* XXX - aren't alerts just shp maps? Why was there special case code? */ if (map_driver_ptr->func) { map_driver_ptr->func(w, dir, filenm, alert, alert_color, destination_pixmap, draw_flags); } XmUpdateDisplay (XtParent (da)); } // End of draw_map() static void index_update_directory(char *directory); static void index_update_accessed(char *filename); ///////////////////////////////////////////////////////////////////// // map_search() // // Function which recurses through map directories, finding map // files. It's called from load_auto_maps and load_alert_maps. If // a map file is found, it is drawn. We can also call this function // in indexing mode rather than draw mode, specified by the // destination_pixmap parameter. // // If alert == NULL, we looking for a regular map file to draw. // If alert != NULL, we have a weather alert to draw. // // For alert maps, we need to do things a bit differently, as there // should be only a few maps that contain all of the alert maps, and we // can compute which map some of them might be in. We need to fill in // the alert structure with the filename that alert is found in. // For alerts we're not drawing the maps, we're just computing the // full filename for the alert and filling that struct field in. // // The "warn" parameter specifies whether to warn the operator about // the alert on the console as well. If it was received locally or // via local RF, then the answer is yes. The severe weather may be // nearby. // // We have the timestamp of the map_index.sys file stored away in // the global: time_t map_index_timestamp; // Use that timestamp to compare the map file or GEO file timestamps // to. Re-index the map if map_index_timestamp is older. // ///////////////////////////////////////////////////////////////////// static void map_search (Widget w, char *dir, alert_entry * alert, int *alert_count,int warn, int destination_pixmap) { struct dirent *dl = NULL; DIR *dm; char fullpath[MAX_FILENAME]; struct stat nfile; // const time_t *ftime; // char this_time[40]; char *ptr; char *map_dir; int map_dir_length; map_draw_flags mdf; // We'll use the weather alert directory if it's an alert map_dir = alert ? ALERT_MAP_DIR : SELECTED_MAP_DIR; map_dir_length = (int)strlen (map_dir); if (alert) // We're doing weather alerts { // First check whether the alert->filename variable is filled // in. If so, we've already found the file and can just display // that shape in the file if (alert->filename[0] == '\0') // No filename in struct, so will have { // to search for the shape in the files. switch (alert->title[3]) { case 'F': // 'F' in 4th char means fire alert // Use fire alert file fz_?????? //fprintf(stderr,"%c:Fire Alert file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "fz"); break; case 'C': // 'C' in 4th char means county // Use County file c_?????? //fprintf(stderr,"%c:County file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "c_"); break; case 'A': // 'A' in 4th char means county warning area // Use County warning area w_????? //fprintf(stderr,"%c:County warning area file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "w_"); break; case 'Z': // Zone, coastal or offshore marine zone file z_????? or mz?????? or oz?????? // oz: ANZ081-086,088,PZZ081-085 // mz: AM,AN,GM,LC,LE,LH,LM,LO,LS,PH,PK,PM,PS,PZ,SL // z_: All others if (strncasecmp(alert->title,"AM",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"AN",2) == 0) { // Need to check for Z081-Z086, Z088, if so use // oz??????, else use mz?????? if ( (strncasecmp(&alert->title[3],"Z081",4) == 0) || (strncasecmp(&alert->title[3],"Z082",4) == 0) || (strncasecmp(&alert->title[3],"Z083",4) == 0) || (strncasecmp(&alert->title[3],"Z084",4) == 0) || (strncasecmp(&alert->title[3],"Z085",4) == 0) || (strncasecmp(&alert->title[3],"Z086",4) == 0) || (strncasecmp(&alert->title[3],"Z088",4) == 0) ) { //fprintf(stderr,"%c:Offshore marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "oz"); } else { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } } else if (strncasecmp(alert->title,"GM",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"LC",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"LE",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"LH",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"LM",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"LO",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"LS",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"PH",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"PK",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"PM",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"PS",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else if (strncasecmp(alert->title,"PZ",2) == 0) { // Need to check for PZZ081-085, if so use oz??????, else use mz?????? if ( (strncasecmp(&alert->title[3],"Z081",4) == 0) || (strncasecmp(&alert->title[3],"Z082",4) == 0) || (strncasecmp(&alert->title[3],"Z083",4) == 0) || (strncasecmp(&alert->title[3],"Z084",4) == 0) || (strncasecmp(&alert->title[3],"Z085",4) == 0) ) { //fprintf(stderr,"%c:Offshore marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "oz"); } else { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } } else if (strncasecmp(alert->title,"SL",2) == 0) { //fprintf(stderr,"%c:Coastal marine zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "mz"); } else { // Must be regular zone file instead of coastal // marine zone or offshore marine zone. //fprintf(stderr,"%c:Zone file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "z_"); } break; default: // VK2XJG // This section could most likely be moved so that it's not called as part of the default, but in order // to get the shapefiles for BOM working this was the best spot at the time... // Australian BOM alerts use the following shapefiles: // PW = Public Warning = gfe_public_weather // MW = Coastal Waters = gfe_coastal_waters // CW = Coastal Waters Warnings = gfe_coastal_waters_warnings // FW = Fire Weather = gfe_fire_weather // ME = Metro Effects = gfe_metro_areas // Note - Need to cater for both 2 and 3 character state designators // Shapefile filenames are static - there is no datestamp in the filename. if ((strncasecmp(&alert->title[4],"MW",2) == 0) || (strncasecmp(&alert->title[3],"MW",2) == 0)) { //fprintf(stderr,"%c:BOM Coastal Waters file\n",alert->title[4]); xastir_snprintf(alert->filename, sizeof(alert->filename), "gfe_coastal_waters.shp"); } else if ((strncasecmp(&alert->title[4],"CW",2) == 0) || (strncasecmp(&alert->title[3],"CW",2) == 0)) { //fprintf(stderr,"%c:BOM Coastal waters warning file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "gfe_coastal_waters_warnings.shp"); } else if ((strncasecmp(&alert->title[4],"PW",2) == 0) || (strncasecmp(&alert->title[3],"PW",2) == 0)) { //fprintf(stderr,"%c:BOM Public Weather file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "gfe_public_weather.shp"); } else if ((strncasecmp(&alert->title[4],"FW",2) == 0) || (strncasecmp(&alert->title[3],"FW",2) == 0)) { //fprintf(stderr,"%c:BOM Fire Weather file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "gfe_fire_weather.shp"); } else if ((strncasecmp(&alert->title[4],"ME",2) == 0) || (strncasecmp(&alert->title[3],"ME",2) == 0)) { //fprintf(stderr,"%c:BOM Metro Areas file\n",alert->title[3]); xastir_snprintf(alert->filename, sizeof(alert->filename), "gfe_metro_areas.shp"); } // Unknown type //fprintf(stderr,"%c:Can't match weather warning to a Shapefile:%s\n",alert->title[3],alert->title); break; } // fprintf(stderr,"%s\t%s\t%s\n",alert->activity,alert->alert_status,alert->title); //fprintf(stderr,"File: %s\n",alert->filename); } // NOTE: Need to skip this part if we have a full filename. if (alert->filename[0]) // We have at least a partial filename { int done = 0; if (strlen(alert->filename) > 3) { done++; // We already have a filename } if (!done) // We don't have a filename yet { // Look through the warning directory to find a match for // the first few characters that we already figured out. // This is designed so that we don't need to know the exact // filename, but only the lead three characters in order to // figure out which shapefile to use. dm = opendir (dir); if (!dm) // Couldn't open directory { xastir_snprintf(fullpath, sizeof(fullpath), "aprsmap %s", dir); // If local alert, warn the operator via the // console as well. if (warn) { perror (fullpath); } } else // We could open the directory just fine { while ( (dl = readdir(dm)) && !done ) { int i; // Check the file/directory name for control // characters for (i = 0; i < (int)strlen(dl->d_name); i++) { // Dump out a warning if control // characters other than LF or CR are // found. if ( (dl->d_name[i] != '\n') && (dl->d_name[i] != '\r') && (dl->d_name[i] < 0x20) ) { fprintf(stderr,"\nmap_search: Found control char 0x%02x in alert file/alert directory name. Line was:\n", dl->d_name[i]); fprintf(stderr,"%s\n",dl->d_name); } /* // This part might not work 'cuz we'd be changing a memory area that // we might have only read access to. Check this. if (dl->d_name[i] < 0x20) { // Terminate string at any control character dl->d_name[i] = '\0'; } */ } xastir_snprintf(fullpath, sizeof(fullpath), "%s%s", dir, dl->d_name); /*fprintf(stderr,"FULL PATH %s\n",fullpath); */ if (stat (fullpath, &nfile) == 0) { // ftime = (time_t *)&nfile.st_ctime; switch (nfile.st_mode & S_IFMT) { case (S_IFDIR): // It's a directory, skip it break; case (S_IFREG): // It's a file, check it /*fprintf(stderr,"FILE %s\n",dl->d_name); */ // Here we look for a match for the // first 2 characters of the filename. // if (strncasecmp(alert->filename,dl->d_name,2) == 0) { // We have a match for the // first few characters. // Check that last three are // "shp" //fprintf(stderr,"%s\n",fullpath); if ( (dl->d_name[strlen(dl->d_name)-3] == 's' || dl->d_name[strlen(dl->d_name)-3] == 'S') && (dl->d_name[strlen(dl->d_name)-2] == 'h' || dl->d_name[strlen(dl->d_name)-2] == 'H') && (dl->d_name[strlen(dl->d_name)-1] == 'p' || dl->d_name[strlen(dl->d_name)-1] == 'P') ) { // We have an exact match. // Save the filename in the alert memcpy(alert->filename, dl->d_name, sizeof(alert->filename)); // Terminate string alert->filename[sizeof(alert->filename)-1] = '\0'; done++; //fprintf(stderr,"%s\n",dl->d_name); } } break; default: // Not dir or file, skip it break; } } } } (void)closedir (dm); } if (done) // We found a filename match for the alert { // Go draw the weather alert (kind'a) //WE7U mdf.draw_filled=1; mdf.usgs_drg=0; if (debug_level & 16) { fprintf(stderr,"map_search: calling draw_map for an alert\n"); } draw_map (w, dir, // Alert directory alert->filename, // Shapefile filename alert, -1, // Signifies "DON'T DRAW THE SHAPE" destination_pixmap, &mdf ); if (debug_level & 16) { fprintf(stderr,"map_search: returned from draw_map\n"); } } else // No filename found that matches the first two { // characters that we already computed. // // Need code here // } } else // Still no filename for the weather alert. { // Output an error message? // // Need code here // } } // MAPS, not alerts else // We're doing regular maps, not weather alerts { time_t map_timestamp; dm = opendir (dir); if (!dm) // Couldn't open directory { xastir_snprintf(fullpath, sizeof(fullpath), "aprsmap %s", dir); if (warn) { perror (fullpath); } } else { int count = 0; while ((dl = readdir (dm))) { int i; // Check the file/directory name for control // characters for (i = 0; i < (int)strlen(dl->d_name); i++) { // Dump out a warning if control characters // other than LF or CR are found. if ( (dl->d_name[i] != '\n') && (dl->d_name[i] != '\r') && (dl->d_name[i] < 0x20) ) { fprintf(stderr,"\nmap_search: Found control char 0x%02x in map file/map directory name. Line was:\n", dl->d_name[i]); fprintf(stderr,"%s\n",dl->d_name); } /* // This part might not work 'cuz we'd be changing a memory area that // we might have only read access to. Check this. if (dl->d_name[i] < 0x20) { // Terminate string at any control character dl->d_name[i] = '\0'; } */ } xastir_snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, dl->d_name); //fprintf(stderr,"FULL PATH %s\n",fullpath); if (stat (fullpath, &nfile) == 0) { // ftime = (time_t *)&nfile.st_ctime; switch (nfile.st_mode & S_IFMT) { case (S_IFDIR): // It's a directory, recurse //fprintf(stderr,"file %c letter %c\n",dl->d_name[0],letter); if ((strcmp (dl->d_name, ".") != 0) && (strcmp (dl->d_name, "..") != 0)) { //fprintf(stderr,"FULL PATH %s\n",fullpath); // If we're indexing, throw the // directory into the map index as // well. if ( (destination_pixmap == INDEX_CHECK_TIMESTAMPS) || (destination_pixmap == INDEX_NO_TIMESTAMPS) ) { char temp_dir[MAX_FILENAME]; // Drop off the base part of the // path for the indexing, // usually // "/usr/local/share/xastir/maps". // Add a '/' to the end. xastir_snprintf(temp_dir, sizeof(temp_dir), "%s/", &fullpath[map_dir_length+1]); // Add the directory to the // in-memory map index. index_update_directory(temp_dir); } // xastir_snprintf(this_time, // sizeof(this_time), // "%s", // ctime(ftime)); map_search(w, fullpath, alert, alert_count, warn, destination_pixmap); } break; case (S_IFREG): // It's a file, draw the map /*fprintf(stderr,"FILE %s\n",dl->d_name); */ // Get the last-modified timestamp for the map file //map_timestamp = (time_t)nfile.st_mtime; map_timestamp = (time_t)( (nfile.st_mtime>nfile.st_ctime) ? nfile.st_mtime : nfile.st_ctime ); // Check whether we're doing indexing or // map drawing. If indexing, we only // want to index if the map timestamp is // newer than the index timestamp. if (destination_pixmap == INDEX_CHECK_TIMESTAMPS || destination_pixmap == INDEX_NO_TIMESTAMPS) { // We're doing indexing, not map drawing char temp_dir[MAX_FILENAME]; // Drop off the base part of the // path for the indexing, usually // "/usr/local/share/xastir/maps". xastir_snprintf(temp_dir, sizeof(temp_dir), "%s", &fullpath[map_dir_length+1]); // Update the "accessed" // variable in the record index_update_accessed(temp_dir); // Note: This is not as efficient as it should be, as we're looking // through the in-memory map index here just to update the // "accessed" variable, then in some cases looking through it again // in the next section for updated maps, or if we're ignoring // timestamps while indexing. Looking through a linear linked list // too many times overall. if ( (destination_pixmap == INDEX_CHECK_TIMESTAMPS) && (map_timestamp < map_index_timestamp) ) { // Map is older than index _and_ // we're supposed to check // timestamps. count++; break; // Skip indexing this file } else // Map is newer or we're ignoring timestamps. { // We'll index the map if (debug_level & 16) { fprintf(stderr,"Indexing map: %s\n",fullpath); } } } // Check whether the file is in a subdirectory if (strncmp (fullpath, map_dir, (size_t)map_dir_length) != 0) { if (debug_level & 16) { fprintf(stderr,"Calling draw_map\n"); } mdf.draw_filled=1; mdf.usgs_drg=0; draw_map (w, dir, dl->d_name, alert ? &alert[*alert_count] : NULL, '\0', destination_pixmap, &mdf ); if (debug_level & 16) { fprintf(stderr,"Returned from draw_map\n"); } if (alert_count && *alert_count) { (*alert_count)--; } } else { // File is in the main map directory // Find the '/' character for (ptr = &fullpath[map_dir_length]; *ptr == '/'; ptr++) ; mdf.draw_filled=1; mdf.usgs_drg=0; if (debug_level & 16) { fprintf(stderr,"Calling draw_map\n"); } draw_map (w, map_dir, ptr, alert ? &alert[*alert_count] : NULL, '\0', destination_pixmap, &mdf ); if (alert_count && *alert_count) { (*alert_count)--; } } count++; break; default: break; } } } if (debug_level & 16) { fprintf(stderr,"Number of maps queried: %d\n", count); } (void)closedir (dm); } } } // List pointer for the map index linked list. map_index_record *map_index_head = NULL; // Might wish to have another variable in the index which is used to // record that a file has been indexed recently. This could be used // to prune old entries out of the index if a full indexing didn't // touch a file entry. Could also delete an entry from the index // if/when a file can't be opened? // Function to dissect and free all of the records in a map index // linked list, leaving it totally empty. // static void free_map_index(map_index_record *index_list_head) { map_index_record *current; map_index_record *temp; current = index_list_head; while (current != NULL) { temp = current; if (current->XmStringPtr != NULL) { XmStringFree(current->XmStringPtr); } current = current->next; free(temp); } index_list_head = NULL; } // Function to copy just the properties fields from the backup map // index to the primary index. Must match each record before // copying. Once it's done, it frees the backup map index. // static void map_index_copy_properties(map_index_record *primary_index_head, map_index_record *backup_index_head) { map_index_record *primary; map_index_record *backup; backup = backup_index_head; // Walk the backup list, comparing the filename field with the // primary list. When a match is found, copy just the // Properties fields (map_layer/draw_filled/auto_maps/selected) // across to the primary record. // while (backup != NULL) { int done = 0; primary = primary_index_head; while (!done && primary != NULL) { if (strcmp(primary->filename, backup->filename) == 0) // If match { if (debug_level & 16) { fprintf(stderr,"Match: %s\t%s\n", primary->filename, backup->filename); } // Copy the Properties across primary->max_zoom = backup->max_zoom; primary->min_zoom = backup->min_zoom; primary->map_layer = backup->map_layer; primary->draw_filled = backup->draw_filled; primary->usgs_drg = backup->usgs_drg; primary->auto_maps = backup->auto_maps; primary->selected = backup->selected; // Done copying this backup record. Go on to the // next. Skip the rest of the primary list for this // iteration. done++; } else // No match, walk the primary list looking for one. { primary = primary->next; } } // Walk the backup list backup = backup->next; } // We're done copying. Free the backup list. free_map_index(backup_index_head); } // Function used to add map directories to the in-memory map index. // Causes an update of the index list in memory. Input Records are // inserted in alphanumerical order. We mark directories in the // index with a '/' on the end of the name, and zero entries for // top/bottom/left/right. // The input directory to this routine MUST have a '/' character on // the end of it. This is how we differentiate directories from // files in the list. static void index_update_directory(char *directory) { map_index_record *current = map_index_head; map_index_record *previous = map_index_head; map_index_record *temp_record = NULL; int done = 0; int i; //fprintf(stderr,"index_update_directory: %s\n", directory ); // Check for initial bad input if ( (directory == NULL) || (directory[0] == '\0') || (directory[strlen(directory) - 1] != '/') || ( (directory[1] == '/') && (strlen(directory) == 1)) ) { fprintf(stderr,"index_update_directory: Bad input: %s\n",directory); return; } // Make sure there aren't any weird characters in the directory // that might cause problems later. Look for control characters // and convert them to string-end characters. for ( i = 0; i < (int)strlen(directory); i++ ) { // Change any control characters to '\0' chars if (directory[i] < 0x20) { fprintf(stderr,"\nindex_update_directory: Found control char 0x%02x in map file/map directory name:\n%s\n", directory[i], directory); directory[i] = '\0'; // Terminate it here } } // Check if the string is _now_ bogus if ( (directory[0] == '\0') || (directory[strlen(directory) - 1] != '/') || ( (directory[1] == '/') && (strlen(directory) == 1))) { fprintf(stderr,"index_update_directory: Bad input: %s\n",directory); return; } //if (map_index_head == NULL) // fprintf(stderr,"Empty list\n"); // Search for a matching directory name in the linked list while ((current != NULL) && !done) { int test; //fprintf(stderr,"Comparing %s to\n %s\n", // current->filename, directory); test = strcmp(current->filename, directory); if (test == 0) { // Found a match! //fprintf(stderr,"Found: Updating entry for %s\n",directory); temp_record = current; done++; // Exit loop, "current" points to found record } else if (test > 0) // Found a string past us in the { // alphabet. Insert ahead of this // last record. //fprintf(stderr,"\n%s\n%s\n",current->filename,directory); //fprintf(stderr,"Not Found: Inserting an index record for %s\n",directory); temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); if (current == map_index_head) // Start of list! { // Insert new record at head of list temp_record->next = map_index_head; map_index_head = temp_record; //fprintf(stderr,"Inserting at head of list\n"); } else // Insert between "previous" and "current" { // Insert new record before "current" previous->next = temp_record; temp_record->next = current; //fprintf(stderr,"Inserting before current\n"); } //fprintf(stderr,"Adding:%d:%s\n",strlen(directory),directory); // Fill in some default values for the new record. temp_record->selected = 0; temp_record->auto_maps = 0; temp_record->XmStringPtr = NULL; //current = current->next; done++; } else // Haven't gotten to the correct insertion point yet { previous = current; // Save ptr to last record current = current->next; } } if (!done) // Matching record not found, add a record to { // the end of the list. "previous" points to // the last record in the list or NULL (empty // list). //fprintf(stderr,"Not Found: Adding an index record for %s\n",directory); temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); temp_record->next = NULL; if (previous == NULL) // Empty list { map_index_head = temp_record; //fprintf(stderr,"First record in new list\n"); } else // Else at end of list { previous->next = temp_record; //fprintf(stderr,"Adding to end of list: %s\n",directory); } //fprintf(stderr,"Adding:%d:%s\n",strlen(directory),directory); // Fill in some default values for the new record. temp_record->selected = 0; temp_record->auto_maps = 0; temp_record->XmStringPtr = NULL; } // Update the values. By this point we have a struct to fill // in, whether it's a new or old struct doesn't matter. Convert // the values from lat/long to Xastir coordinate system. xastir_snprintf(temp_record->filename,MAX_FILENAME,"%s",directory); temp_record->bottom = 0; temp_record->top = 0; temp_record->left = 0; temp_record->right = 0; temp_record->accessed = 1; temp_record->max_zoom = 0; temp_record->min_zoom = 0; temp_record->map_layer = 0; temp_record->draw_filled = 0; temp_record->usgs_drg = 2; } // Function called by the various draw_* functions when in indexing // mode. Causes an update of the index list in memory. Input // parameters are in the Xastir coordinate system due to speed // considerations. Records are inserted in alphanumerical order. void index_update_xastir(char *filename, unsigned long bottom, unsigned long top, unsigned long left, unsigned long right, int default_map_layer) { map_index_record *current = map_index_head; map_index_record *previous = map_index_head; map_index_record *temp_record = NULL; int done = 0; int i; // Check for initial bad input if ( (filename == NULL) || (filename[0] == '\0') || (filename[strlen(filename) - 1] == '/') ) { fprintf(stderr,"index_update_xastir: Bad input: %s\n",filename); return; } // Make sure there aren't any weird characters in the filename // that might cause problems later. Look for control characters // and convert them to string-end characters. for ( i = 0; i < (int)strlen(filename); i++ ) { // Change any control characters to '\0' chars if (filename[i] < 0x20) { fprintf(stderr,"\nindex_update_xastir: Found control char 0x%02x in map file/map directory name:\n%s\n", filename[i], filename); filename[i] = '\0'; // Terminate it here } } // Check if the string is _now_ bogus if (filename[0] == '\0') { fprintf(stderr,"index_update_xastir: Bad input: %s\n",filename); return; } //fprintf(stderr,"index_update_xastir: (%lu,%lu)\t(%lu,%lu)\t%s\n", // bottom, top, left, right, filename ); //if (map_index_head == NULL) // fprintf(stderr,"Empty list\n"); // Skip dbf and shx map extensions. Really should make this // case-independent... if ( strstr(filename,"shx") || strstr(filename,"dbf") || strstr(filename,"SHX") || strstr(filename,"DBF") ) { return; } // Search for a matching filename in the linked list while ((current != NULL) && !done) { int test; //fprintf(stderr,"Comparing %s to\n %s\n",current->filename,filename); test = strcmp(current->filename,filename); if (test == 0) { // Found a match! //fprintf(stderr,"Found: Updating entry for %s\n",filename); temp_record = current; done++; // Exit the while loop } else if (test > 0) // Found a string past us in the { // alphabet. Insert ahead of this // last record. //fprintf(stderr,"\n%s\n%s\n",current->filename,filename); //fprintf(stderr,"Not Found: Inserting an index record for %s\n",filename); temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); if (current == map_index_head) // Start of list! { // Insert new record at head of list temp_record->next = map_index_head; map_index_head = temp_record; //fprintf(stderr,"Inserting at head of list\n"); } else { // Insert new record before "current" previous->next = temp_record; temp_record->next = current; //fprintf(stderr,"Inserting before current\n"); } //fprintf(stderr,"Adding:%d:%s\n",strlen(filename),filename); // Fill in some default values for the new record //WE7U // Here's where we might look at the file extension and assign // default map_layer fields based on that. temp_record->max_zoom = 0; temp_record->min_zoom = 0; temp_record->map_layer = default_map_layer; temp_record->selected = 0; temp_record->XmStringPtr = NULL; if ( strstr(filename,".geo") || strstr(filename,".GEO") || strstr(filename,".Geo")) { temp_record->auto_maps = 0; } else { temp_record->auto_maps = 1; } if ( strstr(filename,".shp") || strstr(filename,".SHP") || strstr(filename,".Shp") ) { temp_record->draw_filled = 2; // Auto } else { temp_record->draw_filled = 0; // No-Fill } if ( strstr(filename,".tif") || strstr(filename,".TIF") || strstr(filename,".Tif") ) { temp_record->usgs_drg = 2; // Auto } else { temp_record->usgs_drg = 0; // No } //current = current->next; done++; } else // Haven't gotten to the correct insertion point yet { previous = current; // Save ptr to last record current = current->next; } } if (!done) // Matching record not found, add a { // record to the end of the list //fprintf(stderr,"Not Found: Adding an index record for %s\n",filename); temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); temp_record->next = NULL; if (previous == NULL) // Empty list { map_index_head = temp_record; //fprintf(stderr,"First record in new list\n"); } else // Else at end of list { previous->next = temp_record; //fprintf(stderr,"Adding to end of list: %s\n",filename); } //fprintf(stderr,"Adding:%d:%s\n",strlen(filename),filename); // Fill in some default values for the new record //WE7U // Here's where we might look at the file extension and assign // default map_layer fields based on that. temp_record->max_zoom = 0; temp_record->min_zoom = 0; temp_record->map_layer = default_map_layer; temp_record->selected = 0; temp_record->XmStringPtr = NULL; if ( strstr(filename,".geo") || strstr(filename,".GEO") || strstr(filename,".Geo")) { temp_record->auto_maps = 0; } else { temp_record->auto_maps = 1; } if ( strstr(filename,".shp") || strstr(filename,".SHP") || strstr(filename,".Shp") ) { temp_record->draw_filled = 2; // Auto } else { temp_record->draw_filled = 0; // No-Fill } if ( strstr(filename,".tif") || strstr(filename,".TIF") || strstr(filename,".Tif") ) { temp_record->usgs_drg = 2; // Auto } else { temp_record->usgs_drg = 0; // No } } // Update the values. By this point we have a struct to fill // in, whether it's a new or old struct doesn't matter. Convert // the values from lat/long to Xastir coordinate system. xastir_snprintf(temp_record->filename,MAX_FILENAME,"%s",filename); temp_record->bottom = bottom; temp_record->top = top; temp_record->left = left; temp_record->right = right; temp_record->accessed = 1; } // Function called by the various draw_* functions when in indexing // mode. Causes an update of the index list in memory. Input // parameters are in lat/long, which are converted to Xastir // coordinates for storage due to speed considerations. Records are // inserted in alphanumerical order. void index_update_ll(char *filename, double bottom, double top, double left, double right, int default_map_layer) { map_index_record *current = map_index_head; map_index_record *previous = map_index_head; map_index_record *temp_record = NULL; int done = 0; unsigned long temp_left, temp_right, temp_top, temp_bottom; int ok; int i; // Check for initial bad input if ( (filename == NULL) || (filename[0] == '\0') || (filename[strlen(filename) - 1] == '/') ) { fprintf(stderr,"index_update_ll: Bad input: %s\n",filename); return; } // Make sure there aren't any weird characters in the filename // that might cause problems later. Look for control characters // and convert them to string-end characters. for ( i = 0; i < (int)strlen(filename); i++ ) { // Change any control characters to '\0' chars if (filename[i] < 0x20) { fprintf(stderr,"\nindex_update_ll: Found control char 0x%02x in map file/map directory name:\n%s\n", filename[i], filename); filename[i] = '\0'; // Terminate it here } } // Check if the string is _now_ bogus if (filename[0] == '\0') { fprintf(stderr,"index_update_ll: Bad input: %s\n",filename); return; } //fprintf(stderr,"index_update_ll: (%15.10g,%15.10g)\t(%15.10g,%15.10g)\t%s\n", // bottom, top, left, right, filename ); //if (map_index_head == NULL) // fprintf(stderr,"Empty list\n"); // Skip dbf and shx map extensions. Really should make this // case-independent... if ( strstr(filename,"shx") || strstr(filename,"dbf") || strstr(filename,"SHX") || strstr(filename,"DBF") ) { return; } // Search for a matching filename in the linked list while ((current != NULL) && !done) { int test; //fprintf(stderr,"Comparing %s to\n %s\n",current->filename,filename); test = strcmp(current->filename,filename); if (test == 0) { // Found a match! //fprintf(stderr,"Found: Updating entry for %s\n",filename); temp_record = current; done++; // Exit the while loop } else if (test > 0) { // Found a string past us in the alphabet. Insert ahead // of this last record. //fprintf(stderr,"\n%s\n%s\n",current->filename,filename); //fprintf(stderr,"Not Found: Inserting an index record for %s\n",filename); temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); if (current == map_index_head) // Start of list! { // Insert new record at head of list temp_record->next = map_index_head; map_index_head = temp_record; //fprintf(stderr,"Inserting at head of list\n"); } else { // Insert new record before "current" previous->next = temp_record; temp_record->next = current; //fprintf(stderr,"Inserting before current\n"); } //fprintf(stderr,"Adding:%d:%s\n",strlen(filename),filename); // Fill in some default values for the new record //WE7U // Here's where we might look at the file extension and assign // default map_layer fields based on that. temp_record->max_zoom = 0; temp_record->min_zoom = 0; temp_record->map_layer = default_map_layer; temp_record->selected = 0; temp_record->XmStringPtr = NULL; if ( strstr(filename,".geo") || strstr(filename,".GEO") || strstr(filename,".Geo")) { temp_record->auto_maps = 0; } else { temp_record->auto_maps = 1; } if ( strstr(filename,".shp") || strstr(filename,".SHP") || strstr(filename,".Shp") ) { temp_record->draw_filled = 2; // Auto } else { temp_record->draw_filled = 0; // No-Fill } if ( strstr(filename,".tif") || strstr(filename,".TIF") || strstr(filename,".Tif") ) { temp_record->usgs_drg = 2; // Auto } else { temp_record->usgs_drg = 0; // No } //current = current->next; done++; } else // Haven't gotten to the correct insertion point yet { previous = current; // Save ptr to last record current = current->next; } } if (!done) // Matching record not found, didn't find alpha { // chars after our string either, add record to // the end of the list. //fprintf(stderr,"Not Found: Adding an index record for %s\n",filename); temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); temp_record->next = NULL; if (previous == NULL) // Empty list { map_index_head = temp_record; //fprintf(stderr,"First record in new list\n"); } else // Else at end of list { previous->next = temp_record; //fprintf(stderr,"Adding to end of list: %s\n",filename); } //fprintf(stderr,"Adding:%d:%s\n",strlen(filename),filename); // Fill in some default values for the new record //WE7U // Here's where we might look at the file extension and assign // default map_layer fields based on that. temp_record->max_zoom = 0; temp_record->min_zoom = 0; temp_record->map_layer = default_map_layer; temp_record->selected = 0; temp_record->XmStringPtr = NULL; if ( strstr(filename,".geo") || strstr(filename,".GEO") || strstr(filename,".Geo")) { temp_record->auto_maps = 0; } else { temp_record->auto_maps = 1; } if ( strstr(filename,".shp") || strstr(filename,".SHP") || strstr(filename,".Shp") ) { temp_record->draw_filled = 2; // Auto } else { temp_record->draw_filled = 0; // No-Fill } if ( strstr(filename,".tif") || strstr(filename,".TIF") || strstr(filename,".Tif") ) { temp_record->usgs_drg = 2; // Auto } else { temp_record->usgs_drg = 0; // No } } // Update the values. By this point we have a struct to fill // in, whether it's a new or old struct doesn't matter. Convert // the values from lat/long to Xastir coordinate system. // In this case the struct uses MAX_FILENAME for the length of // the field, so the below statement is ok. xastir_snprintf(temp_record->filename,MAX_FILENAME,"%s",filename); ok = convert_to_xastir_coordinates( &temp_left, &temp_top, (float)left, (float)top); if (!ok) { fprintf(stderr,"%s\n\n",filename); } ok = convert_to_xastir_coordinates( &temp_right, &temp_bottom, (float)right, (float)bottom); if (!ok) { fprintf(stderr,"%s\n\n",filename); } temp_record->bottom = temp_bottom; temp_record->top = temp_top; temp_record->left = temp_left; temp_record->right = temp_right; temp_record->accessed = 1; } // Function which will update the "accessed" variable on either a // directory or a filename in the map index. static void index_update_accessed(char *filename) { map_index_record *current = map_index_head; int done = 0; int i; // Check for initial bad input if ( (filename == NULL) || (filename[0] == '\0') ) { fprintf(stderr,"index_update_accessed: Bad input: %s\n",filename); return; } // Make sure there aren't any weird characters in the filename // that might cause problems later. Look for control characters // and convert them to string-end characters. for ( i = 0; i < (int)strlen(filename); i++ ) { // Change any control characters to '\0' chars if (filename[i] < 0x20) { fprintf(stderr,"\nindex_update_accessed: Found control char 0x%02x in map file/map directory name:\n%s\n", filename[i], filename); filename[i] = '\0'; // Terminate it here } } // Check if the string is _now_ bogus if (filename[0] == '\0') { fprintf(stderr,"index_update_accessed: Bad input: %s\n",filename); return; } // Skip dbf and shx map extensions. Really should make this // case-independent... if ( strstr(filename,"shx") || strstr(filename,"dbf") || strstr(filename,"SHX") || strstr(filename,"DBF") ) { return; } // Search for a matching filename in the linked list while ((current != NULL) && !done) { int test; //fprintf(stderr,"Comparing %s to\n %s\n",current->filename,filename); test = strcmp(current->filename,filename); if (test == 0) { // Found a match! //fprintf(stderr,"Found: Updating entry for %s\n\n",filename); current->accessed = 1; done++; // Exit the while loop } else // Haven't gotten to the correct insertion point yet { current = current->next; } } } // Function called by map_onscreen_index() // // This function returns: // 0 if the map isn't in the index // 1 if the map is listed in the index // Four parameters listing the extents of the map // // The updated parameters are in the Xastir coordinate system for // speed reasons. // // Note that the index retrieval could be made much faster by // storing the data in a hash instead of a linked list. This is // just an initial implementation to see what speedups are possible. // Hashing might be next. --we7u // // Note that since we've alphanumerically ordered the list, we can // stop when we hit something after this filename in the alphabet. // It speeds things up quite a bit. // // In order to speed this up slightly for the general case, we'll // assume that we'll be fetching indexes in alphabetical order, as // that's how we store them everywhere. We'll save the last map // index pointer away and start searching there each time. That // should make all but the _first_ lookup much faster. // map_index_record *last_index_lookup = NULL; int index_retrieve(char *filename, unsigned long *bottom, unsigned long *top, unsigned long *left, unsigned long *right, int *max_zoom, int *min_zoom, int *map_layer, int *draw_filled, int *usgs_drg, int *auto_maps) { map_index_record *current; int status = 0; if ( (filename == NULL) || (strlen(filename) >= MAX_FILENAME) ) { return(status); } // Attempt to start where we left off last time if (last_index_lookup != NULL) { current = last_index_lookup; } else { current = map_index_head; //fprintf(stderr,"Start at beginning:%s\t", filename); } // Check to see if we're past the correct area. If so, start at // the beginning of the index instead. // if (current && ((current->filename[0] > filename[0]) || (strcmp(current->filename, filename) > 0))) { // // We're past the correct point. Start at the beginning of // the list unless we're already there. // if (current != map_index_head) { current = map_index_head; } //fprintf(stderr,"Start at beginning:%s\t", filename); } // // Search for a matching filename in the linked list. // // Check the first char only. Loop until they match or go past. // This is our high-speed method to get to the correct search // area. // while (current && (current->filename[0] < filename[0])) { // Save the pointer away for next time. There's a reason we // save it before we increment the counter: For "z" weather // alerts, it's nice to have it scan just the very last of // the list before it fails, instead of scanning the entire // list each time and then failing. Need to find out why // weather alerts always fail, and therefore why this // routine gets called every time for them. // last_index_lookup = current; current = current->next; //fprintf(stderr,"1"); } // Stay in this loop while the first char matches. This is our // active search area. // while (current && (current->filename[0] == filename[0])) { int result; // Check the entire string result = strcmp(current->filename, filename); if (result == 0) { // Found a match! status = 1; *bottom = current->bottom; *top = current->top; *left = current->left; *right = current->right; *max_zoom = current->max_zoom; *min_zoom = current->min_zoom; *map_layer = current->map_layer; *draw_filled = current->draw_filled; *usgs_drg = current->usgs_drg; *auto_maps = current->auto_maps; //fprintf(stderr," Found it\n"); return(status); } else if (result > 0) { // We're past it in the index. We didn't find it in the // index. //fprintf(stderr," Did not find1\n"); return(status); } else // Not found yet, look at the next { // Save the pointer away for next time. There's a // reason we save it before we increment the counter: // For "z" weather alerts, it's nice to have it scan // just the very last of the list before it fails, // instead of scanning the entire list each time and // then failing. Need to find out why weather alerts // always fail, and therefore why this routine gets // called every time for them. // last_index_lookup = current; current = current->next; //fprintf(stderr,"2"); } } // We're past the correct search area and didn't find it. //fprintf(stderr," Did not find2\n"); return(status); } // Saves the linked list pointed to by map_index_head to a file. // Keeps the same order as the memory linked list. Delete records // in the in-memory linked list for which the "accessed" variable is // 0 or filename is empty. // void index_save_to_file(void) { FILE *f; map_index_record *current; // map_index_record *last; char out_string[MAX_FILENAME*2]; char map_index_path[MAX_VALUE]; get_user_base_dir(MAP_INDEX_DATA, map_index_path, sizeof(map_index_path)); //fprintf(stderr,"Saving map index to file\n"); f = fopen( map_index_path, "w" ); if (f == NULL) { fprintf(stderr,"Couldn't create/update map index file: %s\n", map_index_path ); return; } current = map_index_head; // last = current; while (current != NULL) { int i; // Make sure there aren't any weird characters in the // filename that might cause problems later. Look for // control characters and convert them to string-end // characters. for ( i = 0; i < (int)strlen(current->filename); i++ ) { // Change any control characters to '\0' chars if (current->filename[i] < 0x20) { fprintf(stderr,"\nindex_save_to_file: Found control char 0x%02x in map name:\n%s\n", current->filename[i], current->filename); current->filename[i] = '\0'; // Terminate it here } } // Save to file if filename non-blank and record has the // accessed field set. if ( (current->filename[0] != '\0') && (current->accessed != 0) ) { // Write each object out to the file as one // comma-delimited line xastir_snprintf(out_string, sizeof(out_string), "%010lu,%010lu,%010lu,%010lu,%05d,%01d,%01d,%01d,%05d,%05d,%s\n", current->bottom, current->top, current->left, current->right, current->map_layer, current->draw_filled, current->usgs_drg, current->auto_maps, current->max_zoom, current->min_zoom, current->filename); if (fprintf(f,"%s",out_string) < (int)strlen(out_string)) { // Failed to write fprintf(stderr,"Couldn't write objects to map index file: %s\n", map_index_path ); current = NULL; // All done } // Set up pointers for next loop iteration // last = current; if (current != NULL) { current = current->next; } } else { // last = current; current = current->next; } /* //WE7U else { // Delete this record from our list! It's a record // for a map file that doesn't exist in the // filesystem anymore. if (last == current) { // We're at the head of the list map_index_head = current->next; // Remember to free the XmStringPtr if we use this bit of code // again. free(current); // Set up pointers for next loop iteration current = map_index_head; last = current; } else { // Not the first record in the list map_index_record *gone; gone = current; // Save ptr to record we wish to delete last->next = current->next; // Unlink from list // Remember to free the XmStringPtr if we use this bit of code // again. free(gone); // Set up pointers for next loop iteration // "last" is still ok current = last->next; } } */ } (void)fclose(f); } // This function is currently not used. // // Function used to add map directories/files to the in-memory map // index. Causes an update of the index list in memory. Input // records are inserted in alphanumerical order. This function is // called from the index_restore_from_file() function below. When // this function is called the new record has all of the needed // information in it. // /* static void index_insert_sorted(map_index_record *new_record) { map_index_record *current = map_index_head; map_index_record *previous = map_index_head; int done = 0; int i; //fprintf(stderr,"index_insert_sorted: %s\n", new_record->filename ); // Check for bad input. if (new_record == NULL) { fprintf(stderr,"index_insert_sorted: Bad input.\n"); return; } // Make sure there aren't any weird characters in the filename // that might cause problems later. Look for any control // characters and convert them to string-end characters. for ( i = 0; i < (int)strlen(new_record->filename); i++ ) { if (new_record->filename[i] < 0x20) { fprintf(stderr,"\nindex_insert_sorted: Found control char 0x%02x in map name:\n%s\n", new_record->filename[i], new_record->filename); new_record->filename[i] = '\0'; // Terminate it here } } // Check if the string is _now_ bogus if (new_record->filename[0] == '\0') { fprintf(stderr,"index_insert_sorted: Bad input.\n"); return; } //if (map_index_head == NULL) // fprintf(stderr,"Empty list\n"); // Search for a matching filename in the linked list while ((current != NULL) && !done) { int test; //fprintf(stderr,"Comparing %s to\n %s\n", // current->filename, new_record->filename); test = strcmp(current->filename, new_record->filename); if (test == 0) { // Found a match! int selected; //fprintf(stderr,"Found a match: Updating entry for %s\n",new_record->filename); // Save this away temporarily. selected = current->selected; // Copy the fields across and then free new_record. We // overwrite the contents of the existing record. xastir_snprintf(current->filename, MAX_FILENAME, "%s", new_record->filename); current->bottom = new_record->bottom; current->top = new_record->top; current->left = new_record->left; current->right = new_record->right; current->accessed = 1; current->max_zoom = new_record->max_zoom; current->min_zoom = new_record->min_zoom; current->map_layer = new_record->map_layer; current->draw_filled = new_record->draw_filled; current->usgs_drg = new_record->usgs_drg; current->selected = selected; // Restore it current->auto_maps = new_record->auto_maps; // Remember to free the XmStringPtr if we use this bit of code // again. free(new_record); // Don't need it anymore done++; // Exit loop, "current" points to found record } else if (test > 0) { // Found a string past us in the // alphabet. Insert ahead of this // last record. //fprintf(stderr,"Not Found, inserting: %s\n", new_record->filename); //fprintf(stderr," Before record: %s\n", current->filename); if (current == map_index_head) { // Start of list! // Insert new record at head of list new_record->next = map_index_head; map_index_head = new_record; //fprintf(stderr,"Inserting at head of list\n"); } else { // Insert between "previous" and "current" // Insert new record before "current" previous->next = new_record; new_record->next = current; //fprintf(stderr,"Inserting before current\n"); } //fprintf(stderr,"Adding:%d:%s\n",strlen(filename),filename); // Fill in some default values for the new record that // don't exist in the map_index.sys file. new_record->selected = 0; if ( strstr(new_record->filename,".geo") || strstr(new_record->filename,".GEO") || strstr(new_record->filename,".Geo") ) { new_record->auto_maps = 0; } else { new_record->auto_maps = 1; } //current = current->next; done++; } else { // Haven't gotten to the correct insertion point yet previous = current; // Save ptr to last record current = current->next; } } if (!done) { // Matching record not found, add the record to // the end of the list. "previous" points to the last // record in the list or NULL (empty list). //fprintf(stderr,"Not Found: Adding to end: %s\n",new_record->filename); new_record->next = NULL; if (previous == NULL) { // Empty list map_index_head = new_record; //fprintf(stderr,"First record in new list\n"); } else { // Else at end of list previous->next = new_record; //fprintf(stderr,"Adding to end of list: %s\n",new_record->filename); } //fprintf(stderr,"Adding:%d:%s\n",strlen(new_record->filename),new_record->filename); // Fill in some default values for the new record. new_record->selected = 0; if ( strstr(new_record->filename,".geo") || strstr(new_record->filename,".GEO") || strstr(new_record->filename,".Geo") ) { new_record->auto_maps = 0; } else { new_record->auto_maps = 1; } } } */ // sort map index // simple bubble sort, since we should be sorted already // static void index_sort(void) { map_index_record *current, *previous, *next; int changed = 1; int loops = 0; // for debug stats previous = map_index_head; next = NULL; // fprintf(stderr, "index_sort: start.\n"); // check if we have any records at all, and at least two if ( (previous != NULL) && (previous->next != NULL) ) { current = previous->next; while ( changed == 1) { changed = 0; if (current->next != NULL) { next = current->next; } if ( strcmp( previous->filename, current->filename) >= 0 ) { // out of order - swap them current->next = previous; previous->next = next; map_index_head = current; current = previous; previous = map_index_head; changed = 1; } while ( next != NULL ) { if ( strcmp( current->filename, next->filename) >= 0 ) { // out of order - swap them current->next = next->next; previous->next = next; next->next = current; // get ready for the next iteration previous = next; // current already moved ahead from the swap next = current->next; changed = 1; } else { previous = current; current = next; next = current->next; } } previous = map_index_head; current = previous->next; next = current->next; loops++; } } // debug stats // fprintf(stderr, "index_sort: ran %d loops.\n", loops); } // Snags the file and creates the linked list pointed to by the // map_index_head pointer. The memory linked list keeps the same // order as the entries in the file. // // NOTE: If we're converting from the old format to the new, we // need to call index_save_to_file() in order to write out the new // format once we're done. // void index_restore_from_file(void) { FILE *f; map_index_record *temp_record; map_index_record *last_record; char in_string[MAX_FILENAME*2]; int doing_migration = 0; char map_index_path[MAX_VALUE]; get_user_base_dir(MAP_INDEX_DATA, map_index_path, sizeof(map_index_path)); //fprintf(stderr,"\nRestoring map index from file\n"); if (map_index_head != NULL) { fprintf(stderr,"Warning: index_restore_from_file(): map_index_head was non-null!\n"); } map_index_head = NULL; // Starting with empty list last_record = NULL; f = fopen( map_index_path, "r" ); if (f == NULL) // No map_index file yet { return; } while (!feof (f)) // Loop through entire map_index file { // Read one line from the file if ( get_line (f, in_string, MAX_FILENAME*2) ) { if (strlen(in_string) >= 15) // We have some data. { // Try to process the // line. char scanf_format[50]; char old_scanf_format[50]; char older_scanf_format[50]; int processed; int i, jj; //fprintf(stderr,"%s\n",in_string); // Tweaked the string below so that it will track // along with MAX_FILENAME-1. We're constructing // the string "%lu,%lu,%lu,%lu,%d,%d,%2000c", where // the 2000 example number is from MAX_FILENAME. xastir_snprintf(scanf_format, sizeof(scanf_format), "%s%d%s", "%lu,%lu,%lu,%lu,%d,%d,%d,%d,%d,%d,%", MAX_FILENAME, "c"); //fprintf(stderr,"%s\n",scanf_format); // index predates addition of usgs_drg flag (26 Jul 2005) xastir_snprintf(old_scanf_format, sizeof(old_scanf_format), "%s%d%s", "%lu,%lu,%lu,%lu,%d,%d,%d,%d,%d,%", MAX_FILENAME, "c"); // index predates addition of min/max zoom (29 Oct 2003) xastir_snprintf(older_scanf_format, sizeof(older_scanf_format), "%s%d%s", "%lu,%lu,%lu,%lu,%d,%d,%d,%", MAX_FILENAME, "c"); // Malloc an index record. We'll add it to the list // only if the data looks reasonable. temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); memset(temp_record->filename, 0, sizeof(temp_record->filename)); temp_record->next = NULL; temp_record->bottom = 64800001l;// Too high temp_record->top = 64800001l; // Too high temp_record->left = 129600001l; // Too high temp_record->right = 129600001l;// Too high temp_record->map_layer = -1; // Too low temp_record->draw_filled = -1; // Too low temp_record->usgs_drg = -1; // Too low temp_record->auto_maps = -1; // Too low temp_record->max_zoom = -1; // Too low temp_record->min_zoom = -1; // Too low temp_record->filename[0] = '\0';// Empty processed = sscanf(in_string, scanf_format, &temp_record->bottom, &temp_record->top, &temp_record->left, &temp_record->right, &temp_record->map_layer, &temp_record->draw_filled, &temp_record->usgs_drg, &temp_record->auto_maps, &temp_record->max_zoom, &temp_record->min_zoom, temp_record->filename); if (processed < 11) { // We're upgrading from an old format index file // that doesn't have usgs_drg. Try the // old_scanf_format string instead. doing_migration = 1; processed = sscanf(in_string, old_scanf_format, &temp_record->bottom, &temp_record->top, &temp_record->left, &temp_record->right, &temp_record->map_layer, &temp_record->draw_filled, &temp_record->auto_maps, &temp_record->max_zoom, &temp_record->min_zoom, temp_record->filename); if (processed < 10) { // It's really old, doesn't have min/max zoom either temp_record->max_zoom = -1; // Too low temp_record->min_zoom = -1; // Too low processed = sscanf(in_string, older_scanf_format, &temp_record->bottom, &temp_record->top, &temp_record->left, &temp_record->right, &temp_record->map_layer, &temp_record->draw_filled, &temp_record->auto_maps, temp_record->filename); } // either way, it doesn't have usgs_drg, so add one // defaulting to Auto if it's a tif file, no if not if ( strstr(temp_record->filename,".tif") || strstr(temp_record->filename,".TIF") || strstr(temp_record->filename,".Tif") ) { temp_record->usgs_drg = 2; // Auto } else { temp_record->usgs_drg = 0; // No } } temp_record->XmStringPtr = NULL; // Do some reasonableness checking on the parameters // we just parsed. //WE7U: First comparison here is always false // if ( (temp_record->bottom < 0l) // || (temp_record->bottom > 64800000l) ) { if (temp_record->bottom > 64800000l) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: bottom extent incorrect %lu in map name:\n%s\n", temp_record->bottom, temp_record->filename); } //WE7U: First comparison here is always false // if ( (temp_record->top < 0l) // || (temp_record->top > 64800000l) ) { if (temp_record->top > 64800000l) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: top extent incorrect %lu in map name:\n%s\n", temp_record->top, temp_record->filename); } //WE7U: First comparison here is always false // if ( (temp_record->left < 0l) // || (temp_record->left > 129600000l) ) { if (temp_record->left > 129600000l) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: left extent incorrect %lu in map name:\n%s\n", temp_record->left, temp_record->filename); } //WE7U: First comparison here is always false // if ( (temp_record->right < 0l) // || (temp_record->right > 129600000l) ) { if (temp_record->right > 129600000l) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: right extent incorrect %lu in map name:\n%s\n", temp_record->right, temp_record->filename); } if ( (temp_record->max_zoom < 0) || (temp_record->max_zoom > 99999) ) { // processed = 0; // Reject this record // fprintf(stderr,"\nindex_restore_from_file: max_zoom field incorrect %d in map name:\n%s\n", // temp_record->max_zoom, // temp_record->filename); // Assign a reasonable value temp_record->max_zoom = 0; //fprintf(stderr,"Assigning max_zoom of 0\n"); } if ( (temp_record->min_zoom < 0) || (temp_record->min_zoom > 99999) ) { // processed = 0; // Reject this record // fprintf(stderr,"\nindex_restore_from_file: min_zoom field incorrect %d in map name:\n%s\n", // temp_record->min_zoom, // temp_record->filename); // Assign a reasonable value temp_record->min_zoom = 0; //fprintf(stderr,"Assigning min_zoom of 0\n"); } if ( (temp_record->map_layer < -99999) || (temp_record->map_layer > 99999) ) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: map_layer field incorrect %d in map name:\n%s\n", temp_record->map_layer, temp_record->filename); } if ( (temp_record->draw_filled < 0) || (temp_record->draw_filled > 2) ) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: draw_filled field incorrect %d in map name:\n%s\n", temp_record->draw_filled, temp_record->filename); } if ( (temp_record->usgs_drg < 0) || (temp_record->usgs_drg > 2) ) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: usgs_drg field incorrect %d in map name:\n%s\n", temp_record->usgs_drg, temp_record->filename); } if ( (temp_record->auto_maps < 0) || (temp_record->auto_maps > 1) ) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: auto_maps field incorrect %d in map name:\n%s\n", temp_record->auto_maps, temp_record->filename); } // Check whether the filename is empty if (strlen(temp_record->filename) == 0) { processed = 0; // Reject this record } // Check for control characters in the filename. // Reject any that have them. jj = (int)strlen(temp_record->filename); for (i = 0; i < jj; i++) { if (temp_record->filename[i] < 0x20) { processed = 0; // Reject this record fprintf(stderr,"\nindex_restore_from_file: Found control char 0x%02x in map name:\n%s\n", temp_record->filename[i], temp_record->filename); } } // Mark the record as accessed at this point. // At the stage where we're writing this list off to // disk, if the record hasn't been accessed by the // re-indexing, it doesn't get written. This has // the effect of flushes deleted files from the // index quickly. temp_record->accessed = 1; // Default is not-selected. Later we read in the // selected_maps.sys file and tweak some of these // fields. temp_record->selected = 0; temp_record->filename[MAX_FILENAME-1] = '\0'; // If correct number of parameters for either old or // new format if (processed == 11 || processed == 10 || processed == 8) { //fprintf(stderr,"Restored: %s\n",temp_record->filename); // Insert the new record into the in-memory map // list in sorted order. // --slow for large lists // index_insert_sorted(temp_record); // -- so we just add it to the end of the list // and sort it at the end tp make sure nobody // messed us up by editting the file by hand if ( last_record == NULL ) // first record { map_index_head = temp_record; } else { last_record->next = temp_record; } last_record = temp_record; // Remember that we may just have attached the // record to our in-memory map list, or we may // have free'ed it in the above function call. // Set the pointer to NULL to make sure we don't // try to do anything else with the memory. temp_record = NULL; } else // sscanf didn't parse the proper number of { // items. Delete the record. // Remember to free the XmString pointer if necessary. free(temp_record); // fprintf(stderr,"index_restore_from_file:sscanf parsing error\n"); } } } } (void)fclose(f); // now that we have read the whole file, make sure it is sorted index_sort(); // probably should check for dup records if (doing_migration) { // Save in new file format if we just did a migration from // old format to new. fprintf(stderr,"Migrating from old map_index.sys format to new format.\n"); index_save_to_file(); } } // map_indexer() // // Recurses through the map directories finding map extents // and recording them in the map index. Once the indexing is // complete, write the current index out to a file. // // It'd be nice to call index_restore_from_file() from main.c:main() // so that an earlier copy of the index is restored before the map // display is created. // // If we set the "accessed" variable in the in-memory index to 0 for // each record and then run the indexer, the save-to-file function // will delete those with a value of 0 when writing to disk. Those // maps no longer exist in the filesystem and should be deleted. We // could either wipe them from the in-memory database at that time // as well, or wipe the whole list and re-read it from disk to get // the current list. // // If parameter is 0, we'll do the smart timestamp-checking // indexing. // If 1, we'll erase the in-memory index and do full indexing. // void map_indexer(int parameter) { struct stat nfile; int check_times = 1; FILE *f; map_index_record *current; map_index_record *backup_list_head = NULL; char map_index_path[MAX_VALUE]; get_user_base_dir(MAP_INDEX_DATA, map_index_path, sizeof(map_index_path)); if (debug_level & 16) { fprintf(stderr,"map_indexer() start\n"); } fprintf(stderr,"Indexing maps...\n"); #ifdef HAVE_LIBSHP // get rid of stored dbfawk signatures and force reload. clear_dbfawk_sigs(); #endif // Find the timestamp on the index file first. Save it away so // that the timestamp for each map file can be compared to it. if (stat ( map_index_path, &nfile) != 0) { // File doesn't exist yet. Create it. f = fopen( map_index_path, "w" ); if (f != NULL) { (void)fclose(f); } else fprintf(stderr,"Couldn't create map index file: %s\n", map_index_path ); check_times = 0; // Don't check the timestamps. Do them all. } else // File exists { map_index_timestamp = (time_t)nfile.st_mtime; check_times = 1; } if (parameter == 1) // Full indexing instead of timestamp-check indexing { // Move the in-memory index to a backup pointer backup_list_head = map_index_head; map_index_head = NULL; // // Set the timestamp to 0 so that everything gets indexed // map_index_timestamp = (time_t)0l; check_times = 0; } // Set the "accessed" field to zero for every record in the // index. Note that the list could be empty at this point. current = map_index_head; while (current != NULL) { current->accessed = 0; current = current->next; } if (check_times) { if (debug_level & 16) { fprintf(stderr,"map_indexer: Calling map_search\n"); } map_search (NULL, AUTO_MAP_DIR, NULL, NULL, (int)FALSE, INDEX_CHECK_TIMESTAMPS); if (debug_level & 16) { fprintf(stderr,"map_indexer: Returned from map_search\n"); } } else { if (debug_level & 16) { fprintf(stderr,"map_indexer: Calling map_search\n"); } map_search (NULL, AUTO_MAP_DIR, NULL, NULL, (int)FALSE, INDEX_NO_TIMESTAMPS); if (debug_level & 16) { fprintf(stderr,"map_indexer: Returned from map_search\n"); } } if (debug_level & 16) { fprintf(stderr,"map_indexer() middle\n"); } if (parameter == 1) // Full indexing instead of timestamp-check indexing { // Copy the Properties from the backup list to the new list, // then free the backup list. map_index_copy_properties(map_index_head, backup_list_head); } // Save the updated index to the file index_save_to_file(); fprintf(stderr,"Finished indexing maps\n"); if (debug_level & 16) { fprintf(stderr,"map_indexer() end\n"); } } /* moved these here and made them static so it will function on FREEBSD */ #define MAX_ALERT 7000 // If we comment this out, we link, but get a segfault at runtime. // Take out the "static" and we get a segfault when we zoom out too // far with the lakes or counties shapefile loaded. No idea why // yet. --we7u //static alert_entry alert[MAX_ALERT]; static int alert_count; /******************************************************************* * fill_in_new_alert_entries() * * Fills in the index and filename portions of any alert entries * that are missing them. This function should be called at the * point where we've just received a new weather alert. * //WE7U // Later we should change this so that it doesn't scan the entire // message list, but is passed the important info directly from the // decode routines in db.c, and the message should NOT be added to // the message list. //WE7U * * This function is designed to use ESRI Shapefile map files. The * base directory where the Shapefiles are located is passed to us * in the "dir" variable. * * map_search() fills in the filename field of the alert struct. * draw_shapefile_map() fills in the index field. *******************************************************************/ void fill_in_new_alert_entries() { // int ii; char alert_scan[MAX_FILENAME]; // char *dir_ptr; struct hashtable_itr *iterator = NULL; alert_entry *temp; char dir[MAX_FILENAME]; if (debug_level & 2) { fprintf(stderr,"fill_in_new_alert_entries start\n"); } xastir_snprintf(dir, sizeof(dir), "%s", ALERT_MAP_DIR); alert_count = MAX_ALERT - 1; // Set up our path to the wx alert maps memset(alert_scan, 0, sizeof (alert_scan)); // Zero our alert_scan string xastir_snprintf(alert_scan, // Fetch the base directory sizeof(alert_scan), "%s", dir); strncat(alert_scan, // Complete alert directory is now set up in the string "/", sizeof(alert_scan) - 1 - strlen(alert_scan)); // dir_ptr = &alert_scan[strlen (alert_scan)]; // Point to end of path // Iterate through the weather alerts. It looks like we wish to // just fill in the alert struct and to determine whether the // alert is within our viewport here. We don't wish to draw the // alerts at this stage, that happens in the load_alert_maps() // function below. iterator = create_wx_alert_iterator(); temp = get_next_wx_alert(iterator); while (iterator != NULL && temp) { if (!temp->filename[0]) // Filename is { // empty, we need to fill it in. // fprintf(stderr,"fill_in_new_alert_entries() Title: %s\n",temp->title); // The last parameter denotes loading into // pixmap_alerts instead of pixmap or pixmap_final. // Note that just calling map_search does not get // the alert areas drawn on the screen. The // draw_map() function called by map_search just // fills in the filename field in the struct and // exits. // // The "warn" parameter (next to last) specifies whether // to dump warnings out to the console as well. If the // warning was received on local RF or locally, warn the // operator (the weather must be near). map_search (da, alert_scan, temp, &alert_count, (int)temp->flags[source], DRAW_TO_PIXMAP_ALERTS); // fprintf(stderr,"fill_in_new_alert_entries() Title1:%s\n",temp->title); } temp = get_next_wx_alert(iterator); } #ifndef USING_LIBGC //fprintf(stderr,"free iterator 4\n"); if (iterator) { free(iterator); } #endif // USING_LIBGC if (debug_level & 2) { fprintf(stderr,"fill_in_new_alert_entries end\n"); } } /******************************************************************* * load_alert_maps() * * Used to load weather alert maps, based on NWS weather alerts that * are received. Called from create_image() and refresh_image(). * This function is designed to use ESRI Shapefile map files. The * base directory where the Shapefiles are located is passed to us * in the "dir" variable. * * map_search() fills in the filename field of the alert struct. * draw_shapefile_map() fills in the index field. *******************************************************************/ void load_alert_maps (Widget w, char *dir) { // int ii; int level; unsigned char fill_color[] = { (unsigned char)0x69, // gray86 (unsigned char)0x4a, // red2 (unsigned char)0x63, // yellow2 (unsigned char)0x66, // cyan2 (unsigned char)0x61, // RoyalBlue (unsigned char)0x64, // ForestGreen (unsigned char)0x62 }; // orange3 struct hashtable_itr *iterator = NULL; alert_entry *temp; map_draw_flags mdf; //fprintf(stderr,"load_alert_maps\n"); // TODO: // Figure out how to pass a quantity of zones off to the map drawing // routines, then we can draw them all with one pass through each // map file. Alphanumerically sort the zones to make it easier for // the map drawing functions? Note that the indexing routines fill // in both the filename and the shapefile index for each record. // // Alternative: Call map_draw for each filename listed and have the // draw_shapefile function iterate through the array looking for all // filename matches, pulling non-negative indexes out of each index // field for matches and drawing them. That should be fast and // require no sorting of the array. Downside: The alerts won't be // layered based on alert level unless we modify the above: Drawing // each file once for each alert-level in the proper layering order. // Perhaps we could keep a list of which filenames have been called, // and only call each one once per load_alert_maps() call. // Just for a test //draw_shapefile_map (w, dir, filenm, alert, alert_color, destination_pixmap); //draw_shapefile_map (w, dir, "c_16my01.shp", NULL, '\0', DRAW_TO_PIXMAP_ALERTS); // Are we drawing them in reverse order so that the important // alerts end up drawn on top of the less important alerts? // Actually, since the alert hash isn't ordered, perhaps we need to // order them by priority, then by map file, so that we can draw the // shapes from each map file in the correct order. This might cause // each map file to be drawn up to three times (once for each // priority level), but that's better than calling each map for each // zone as is done now. iterator = create_wx_alert_iterator(); temp = get_next_wx_alert(iterator); while (iterator != NULL && temp) { HandlePendingEvents(app_context); if (interrupt_drawing_now) { #ifndef USING_LIBGC //fprintf(stderr,"free iterator 5\n"); if (iterator) { free(iterator); } #endif // USING_LIBGC return; } if (disable_all_maps) { #ifndef USING_LIBGC //fprintf(stderr,"free iterator 6\n"); if (iterator) { free(iterator); } #endif // USING_LIBGC return; } // Check whether the alert slot is filled/empty if (temp->title[0] == '\0') // Empty slot { temp = get_next_wx_alert(iterator); continue; } if ( (level = alert_active(temp, ALERT_ALL) ) ) { if (level >= (int)sizeof (fill_color)) { level = 0; } // The last parameter denotes drawing into pixmap_alert // instead of pixmap or pixmap_final. if (debug_level & 16) { fprintf(stderr,"load_alert_maps() Drawing %s\n",temp->filename); fprintf(stderr,"load_alert_maps() Title4:%s\n",temp->title); } // Attempt to draw alert if ( temp->index != -1 ) // Shape found in shapefile { // Check whether we've ever tried to draw this alert // before. If not, attempt it and get the boundary // limits filled in. // if ( temp->bottom_boundary == 0.0 && temp->top_boundary == 0.0 && temp->left_boundary == 0.0 && temp->right_boundary == 0.0) { if (temp->alert_level != 'C') { draw_map (w, dir, temp->filename, temp, fill_color[level], DRAW_TO_PIXMAP_ALERTS, &mdf); // draw filled } } if (map_visible_lat_lon(temp->bottom_boundary, // Shape visible temp->top_boundary, temp->left_boundary, temp->right_boundary) ) { if (temp->alert_level != 'C') // Alert not cancelled { mdf.draw_filled=1; mdf.usgs_drg=0; if (debug_level & 16) { fprintf(stderr,"load_alert_maps: Calling draw_map\n"); } draw_map (w, dir, temp->filename, temp, fill_color[level], DRAW_TO_PIXMAP_ALERTS, &mdf); // draw filled } if (temp) { temp->flags[on_screen] = 'Y'; } } else { // Not in our viewport, don't draw it! if (debug_level & 16) { fprintf(stderr,"load_alert_maps() Alert not visible\n"); } //fprintf(stderr,"B:%f T:%f L:%f R:%f\n", temp->bottom_boundary, temp->top_boundary, temp->left_boundary, temp->right_boundary); if (temp) { temp->flags[on_screen] = 'N'; } } } else { // Can't find this shape in the shapefile. if (debug_level & 16) { fprintf(stderr, "load_alert_maps() Shape %s, strlen=%d, not found in %s\n", temp->title, (int)strlen(temp->title), temp->filename ); } } } temp = get_next_wx_alert(iterator); } #ifndef USING_LIBGC //fprintf(stderr,"free iterator 7\n"); if (iterator) { free(iterator); } #endif // USING_LIBGC if (debug_level & 16) { fprintf(stderr,"load_alert_maps() Done drawing all active alerts\n"); } if (alert_display_request()) { alert_redraw_on_update = redraw_on_new_data = 2; } } // Here's the head of our sorted-by-layer maps list static map_index_record *map_sorted_list_head = NULL; static void empty_map_sorted_list(void) { map_index_record *current = map_sorted_list_head; while (map_sorted_list_head != NULL) { current = map_sorted_list_head; map_sorted_list_head = current->next; if (current->XmStringPtr != NULL) { XmStringFree(current->XmStringPtr); } free(current); } } // Insert a map into the list at the end of the maps with the same // layer number. We'll need to look up the parameters for it from // the master map_index list and then attach a new record to our new // sorted list in the proper place. // // This function should be called when we're first starting up // Xastir and anytime that selected_maps.sys is changed. // static void insert_map_sorted(char *filename) { map_index_record *current; map_index_record *last; map_index_record *temp_record; unsigned long bottom; unsigned long top; unsigned long left; unsigned long right; int max_zoom; int min_zoom; int map_layer; int draw_filled; int usgs_drg; int auto_maps; int done; if (index_retrieve(filename, &bottom, &top, &left, &right, &max_zoom, &min_zoom, &map_layer, &draw_filled, &usgs_drg, &auto_maps)) // Found a match { // Allocate a new record temp_record = (map_index_record *)malloc(sizeof(map_index_record)); CHECKMALLOC(temp_record); // Fill in the values xastir_snprintf(temp_record->filename,MAX_FILENAME,"%s",filename); temp_record->bottom = bottom; temp_record->top = top; temp_record->left = left; temp_record->right = right; temp_record->max_zoom = max_zoom; temp_record->min_zoom = min_zoom; temp_record->map_layer = map_layer; temp_record->draw_filled = draw_filled; temp_record->usgs_drg = usgs_drg; temp_record->auto_maps = auto_maps; temp_record->selected = 1; // Always, we already know this! temp_record->accessed = 0; temp_record->next = NULL; temp_record->XmStringPtr = NULL; // Now find the proper place for it and insert it in // layer-order into the list. current = map_sorted_list_head; last = map_sorted_list_head; done = 0; // Possible cases: // Empty list // insert at beginning of list // insert at end of list // insert between other entries if (map_sorted_list_head == NULL) { // Empty list. Insert record. map_sorted_list_head = temp_record; done++; } else if (map_layer < current->map_layer) { // Insert at beginning of list temp_record->next = current; map_sorted_list_head = temp_record; done++; } else // Need to insert between records or at end of list { while (!done && (current != NULL) ) { if (map_layer >= current->map_layer) // Not to our layer yet { last = current; current = current->next; // May point to NULL now } else if (map_layer < current->map_layer) { temp_record->next = current; last->next = temp_record; done++; } } } // Handle running off the end of the list if (!done && (current == NULL) ) { last->next = temp_record; } } else { // We failed to find it in the map index } } /********************************************************** * load_auto_maps() * * NEW: Uses the in-memory map_index to scan through the * maps. * * OLD: Recurses through the map directories looking for * maps to load. **********************************************************/ void load_auto_maps (Widget w, char * UNUSED(dir) ) { map_index_record *current = map_index_head; map_draw_flags mdf; HandlePendingEvents(app_context); if (interrupt_drawing_now) { return; } // Skip the sorting of the maps if we don't need to do it if (re_sort_maps) { //fprintf(stderr,"*** Sorting the selected maps by layer...\n"); // Empty the sorted list first. We'll create a new one. empty_map_sorted_list(); // Run through the entire map_index linked list while (current != NULL) { if (auto_maps_skip_raster && ( strstr(current->filename,".geo") || strstr(current->filename,".GEO") || strstr(current->filename,".Geo") || strstr(current->filename,".tif") || strstr(current->filename,".TIF") || strstr(current->filename,".Tif"))) { // Skip this map } else // Draw this map { //fprintf(stderr,"Loading: %s/%s\n",SELECTED_MAP_DIR,current->filename); //WE7U insert_map_sorted(current->filename); /* draw_map (w, SELECTED_MAP_DIR, current->filename, NULL, '\0', DRAW_TO_PIXMAP); */ } current = current->next; } // All done sorting until something is changed in the Map // Chooser. re_sort_maps = 0; //fprintf(stderr,"*** DONE sorting the selected maps.\n"); } // We have the maps in sorted order. Run through the list and // draw them. Only include those that have the auto_maps field // set to 1. current = map_sorted_list_head; while (current != NULL) { HandlePendingEvents(app_context); if (interrupt_drawing_now) { // Update to screen (void)XCopyArea(XtDisplay(da), pixmap, XtWindow(da), gc, 0, 0, (unsigned int)screen_width, (unsigned int)screen_height, 0, 0); return; } if (disable_all_maps) { // Update to screen (void)XCopyArea(XtDisplay(da), pixmap, XtWindow(da), gc, 0, 0, (unsigned int)screen_width, (unsigned int)screen_height, 0, 0); return; } // Debug // fprintf(stderr,"Drawing level:%05d, file:%s\n", // current->map_layer, // current->filename); // Draw the maps in sorted-by-layer order if (current->auto_maps) { mdf.draw_filled = current->draw_filled; mdf.usgs_drg = current->usgs_drg; if (debug_level & 16) { fprintf(stderr,"load_auto_maps: Calling draw_map\n"); } draw_map (w, SELECTED_MAP_DIR, current->filename, NULL, '\0', DRAW_TO_PIXMAP, &mdf); } current = current->next; } } /******************************************************************* * load_maps() * * Loads maps, draws grid, updates the display. * * We now create a linked list of maps in layer-order and use this * list to draw the maps. This preserves the correct ordering in * all cases. The layer to draw each map is specified in the * map_index.sys file (fifth parameter). Eventually code will be * added to the Map Chooser in order to change the layer each map is * drawn at. *******************************************************************/ void load_maps (Widget w) { FILE *f; char mapname[MAX_FILENAME]; int i; char selected_dir[MAX_FILENAME]; map_index_record *current; map_draw_flags mdf; char selected_map_path[MAX_VALUE]; get_user_base_dir(SELECTED_MAP_DATA, selected_map_path, sizeof(selected_map_path)); // int dummy; if (debug_level & 16) { fprintf(stderr,"Load maps start\n"); } HandlePendingEvents(app_context); if (interrupt_drawing_now) { // Update to screen (void)XCopyArea(XtDisplay(da), pixmap, XtWindow(da), gc, 0, 0, (unsigned int)screen_width, (unsigned int)screen_height, 0, 0); return; } // Skip the sorting of the maps if we don't need to do it if (re_sort_maps) { //fprintf(stderr,"*** Sorting the selected maps by layer...\n"); // Empty the sorted list first. We'll create a new one. empty_map_sorted_list(); // Make sure the string is empty before we start selected_dir[0] = '\0'; // Create empty file if it doesn't exist (void)filecreate( selected_map_path ); f = fopen ( selected_map_path, "r" ); if (f != NULL) { if (debug_level & 16) { fprintf(stderr,"Load maps Open map file\n"); } while (!feof (f)) { // Grab one line from the file if ( fgets( mapname, MAX_FILENAME-1, f ) != NULL ) { // Forced termination (just in case) mapname[MAX_FILENAME-1] = '\0'; // Get rid of the newline at the end for (i = strlen(mapname); i > 0; i--) { if (mapname[i] == '\n') { mapname[i] = '\0'; } } if (debug_level & 16) { fprintf(stderr,"Found mapname: %s\n", mapname); } // Test for comment if (mapname[0] != '#') { // Check whether it's a directory that was // selected. If so, save it in a special // variable and use that to match all the files // inside the directory. Note that with the way // we have things ordered in the list, the // directories appear before their member files. if (mapname[strlen(mapname)-1] == '/') { int len; // Found a directory. Save the name. xastir_snprintf(selected_dir, sizeof(selected_dir), "%s", mapname); len = strlen(mapname); //fprintf(stderr,"Selected %s directory\n",selected_dir); // Here we need to run through the map_index // list to find all maps that match the // currently selected directory. Attempt to // load all of those maps as well. //fprintf(stderr,"Load all maps under this directory: %s\n",selected_dir); // Point to the start of the map_index list current = map_index_head; while (current != NULL) { if (strncmp(current->filename,selected_dir,len) == 0) { if (current->filename[strlen(current->filename)-1] != '/') { //fprintf(stderr,"Loading: %s\n",current->filename); //WE7U insert_map_sorted(current->filename); /* draw_map (w, SELECTED_MAP_DIR, current->filename, NULL, '\0', DRAW_TO_PIXMAP); */ } } current = current->next; } } // Else must be a regular map file else { //fprintf(stderr,"%s\n",mapname); //start_timer(); //WE7U insert_map_sorted(mapname); /* draw_map (w, SELECTED_MAP_DIR, mapname, NULL, '\0', DRAW_TO_PIXMAP); */ //stop_timer(); //print_timer_results(); if (debug_level & 16) { fprintf(stderr,"Load maps -%s\n", mapname); } XmUpdateDisplay (da); } } } else // We've hit EOF { break; } } (void)fclose (f); statusline(" ",1); // delete status line } else { fprintf(stderr,"Couldn't open file: %s\n", selected_map_path ); } // All done sorting until something is changed in the Map // Chooser. re_sort_maps = 0; //fprintf(stderr,"*** DONE sorting the selected maps.\n"); } // We have the maps in sorted order. Run through the list and // draw them. current = map_sorted_list_head; while (current != NULL) { HandlePendingEvents(app_context); if (interrupt_drawing_now) { statusline(" ",1); // delete status line // Update to screen (void)XCopyArea(XtDisplay(da), pixmap, XtWindow(da), gc, 0, 0, (unsigned int)screen_width, (unsigned int)screen_height, 0, 0); return; } if (disable_all_maps) { // Update to screen (void)XCopyArea(XtDisplay(da), pixmap, XtWindow(da), gc, 0, 0, (unsigned int)screen_width, (unsigned int)screen_height, 0, 0); return; } // Debug // fprintf(stderr,"Drawing level:%05d, file:%s\n", // current->map_layer, // current->filename); // Draw the maps in sorted-by-layer order mdf.draw_filled = current->draw_filled; mdf.usgs_drg = current->usgs_drg; if (debug_level & 16) { fprintf(stderr,"load_maps: Calling draw_map\n"); } // Map profiling, set up for 800x600 window at "Map Profile Test // Site" bookmark. // // Loading "rd011802.shp" 500 times takes // 302->256->264->269->115->116 seconds. // // 100 times on PP200 takes 192->183 seconds. // //start_timer(); //fprintf(stderr,"Calling draw_map() 500 times...\n"); //for (dummy = 0; dummy < 500; dummy++) { draw_map (w, SELECTED_MAP_DIR, current->filename, NULL, '\0', DRAW_TO_PIXMAP, &mdf); //} //stop_timer(); print_timer_results(); current = current->next; } if (debug_level & 16) { fprintf(stderr,"Load maps stop\n"); } }