/** * Write the TIN to a TIN (.itf) file (VTP-defined format). */ bool vtTin::Write(const char *fname, bool progress_callback(int)) const { FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; char *wkt; OGRErr err = m_proj.exportToWkt(&wkt); if (err != OGRERR_NONE) { fclose(fp); return false; } int proj_len = strlen(wkt); int data_start = 5 + 4 + 4 + 4 + + 4 + proj_len + 32 + 4 + 4; int i; int verts = NumVerts(); int tris = NumTris(); fwrite("tin02", 5, 1, fp); // version 2 fwrite(&verts, 4, 1, fp); fwrite(&tris, 4, 1, fp); fwrite(&data_start, 4, 1, fp); fwrite(&proj_len, 4, 1, fp); fwrite(wkt, proj_len, 1, fp); OGRFree(wkt); // version 2 of the format has extents: left, top, right, bottom, min z, max h fwrite(&m_EarthExtents.left, sizeof(double), 4, fp); fwrite(&m_fMinHeight, sizeof(float), 1, fp); fwrite(&m_fMaxHeight, sizeof(float), 1, fp); // room for future extention: you can add fields here, as long as you // increase the data_start offset above accordingly // count progress int count = 0, total = verts + tris; // write verts for (i = 0; i < verts; i++) { fwrite(&m_vert[i].x, 8, 2, fp); // 2 doubles fwrite(&m_z[i], 4, 1, fp); // 1 float if (progress_callback && (++count % 100) == 0) progress_callback(count * 99 / total); } // write tris for (i = 0; i < tris; i++) { fwrite(&m_tri[i*3], 4, 3, fp); // 3 ints if (progress_callback && (++count % 100) == 0) progress_callback(count * 99 / total); } fclose(fp); return true; }
void Countries::WriteGCF(const char *fname) { FILE *fp = vtFileOpen(fname, "wb"); int num = m_countries.GetSize(); fwrite(&num, sizeof(int), 1, fp); int i, j, num_places; for (i = 0; i < num; i++) { Country *country = m_countries[i]; num_places = country->m_places.GetSize(); WriteString(fp, country->m_full); fwrite(&num_places, sizeof(int), 1, fp); for (j = 0; j < num_places; j++) { Place *place = country->m_places[j]; fwrite(&place->m_pos.x, sizeof(double), 2, fp); WriteString(fp, place->m_fullname_nd); } } fclose(fp); }
bool Gazetteer::ReadZips(const char *fname) { // safety checks if (!fname) return false; if (*fname == 0) return false; FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; vtString str; char buf[999]; while (fgets(buf, 999, fp) != NULL) { Zip p; str = Grab(buf, 2, 5); if (str[4] == 'H' || str[4] == 'X') continue; p.m_zip = atoi(str); double x = atof(buf+146); double y = atof(buf+136); p.geo.Set(x, y); m_zips.push_back(p); } fclose(fp); return true; }
bool Gazetteer::ReadPlaces(const char *fname) { // safety checks if (!fname) return false; if (*fname == 0) return false; FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; char buf[999]; while (fgets(buf, 999, fp) != NULL) { Place p; p.m_state = Grab(buf, 0, 2); p.m_name = Grab(buf, 9, 60); p.m_name.TrimRight(); if (p.m_name.Right(10) == " (balance)") p.m_name = p.m_name.Left(p.m_name.GetLength() - 10); if (p.m_name.Right(5) == " city") p.m_name = p.m_name.Left(p.m_name.GetLength() - 5); else if (p.m_name.Right(5) == " town") p.m_name = p.m_name.Left(p.m_name.GetLength() - 5); else if (p.m_name.Right(4) == " CDP") p.m_name = p.m_name.Left(p.m_name.GetLength() - 4); else if (p.m_name.Right(13) == " municipality") p.m_name = p.m_name.Left(p.m_name.GetLength() - 13); else if (p.m_name.Right(17) == " city and borough") p.m_name = p.m_name.Left(p.m_name.GetLength() - 17); else if (p.m_name.Right(8) == " borough") p.m_name = p.m_name.Left(p.m_name.GetLength() - 8); else if (p.m_name.Right(8) == " village") p.m_name = p.m_name.Left(p.m_name.GetLength() - 8); else if (p.m_name.Right(10) == " comunidad") p.m_name = p.m_name.Left(p.m_name.GetLength() - 10); else if (p.m_name.Right(12) == " zona urbana") p.m_name = p.m_name.Left(p.m_name.GetLength() - 12); else if (p.m_name.Right(7) == " urbana") p.m_name = p.m_name.Left(p.m_name.GetLength() - 7); else { int foo = 1; } double x = atof(buf+153); double y = atof(buf+143); p.geo.Set(x, y); m_places.push_back(p); } fclose(fp); return true; }
bool vtUnzip::ExtractAccept(const char *write_filename, bool bOverwrite) { bool bOK = true; if (!bOverwrite) { FILE* file = vtFileOpen(write_filename, "rb"); bool bExisting = (file != NULL); if (file != NULL) fclose(file); bOK = !bExisting; } return bOK; }
/** * Write the TIN to a Wavefront OBJ file. Note that we write X and Y as * geographic coordinates, but OBJ only supports single-precision floating * point values, so it may lose some precision. */ bool vtTin::WriteOBJ(const char *fname, bool progress_callback(int)) const { FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; int i, count = 0; const int verts = NumVerts(); const int tris = NumTris(); const int total = verts + tris; fprintf(fp, "####\n"); fprintf(fp, "#\n"); fprintf(fp, "# OBJ File Generated by VTBuilder\n"); fprintf(fp, "#\n"); fprintf(fp, "####\n"); fprintf(fp, "# Object %s\n", fname); fprintf(fp, "#\n"); fprintf(fp, "# Vertices: %d\n", verts); fprintf(fp, "# Faces: %d\n", tris); fprintf(fp, "#\n"); fprintf(fp, "####\n"); // write verts for (i = 0; i < verts; i++) { fprintf(fp, "v %lf %lf %f\n", m_vert[i].x, m_vert[i].y, m_z[i]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, "# %d vertices, 0 vertices normals\n", verts); fprintf(fp, "\n"); // write tris for (i = 0; i < tris; i++) { // Here is triangle definition (zero based) A B C ... // the indices in the file are 1-based, so add 1 fprintf(fp, "f %d %d %d\n", m_tri[i*3+0]+1, m_tri[i*3+1]+1, m_tri[i*3+2]+1); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, "# %d faces, 0 coords texture\n", tris); fprintf(fp, "\n"); fprintf(fp, "# End of File\n"); fclose(fp); return true; }
bool ColorMap::Load(const char *fname) { // watch out for %f LocaleWrap normal_numbers(LC_NUMERIC, "C"); FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; char buf[80]; fgets(buf, 80, fp); if (strncmp(buf, "colormap1", 9)) return false; while (fgets(buf, 80, fp) != NULL) { int ival; if (!strncmp(buf, "blend", 5)) { sscanf(buf, "blend: %d\n", &ival); m_bBlend = (ival != 0); } else if (!strncmp(buf, "relative", 8)) { sscanf(buf, "relative: %d\n", &ival); m_bRelative = (ival != 0); } else if (!strncmp(buf, "size", 4)) { int size; sscanf(buf, "size %d\n", &size); m_elev.resize(size); m_color.resize(size); for (int i = 0; i < size; i++) { float f; short r, g, b; fscanf(fp, "\telev %f color %hd %hd %hd\n", &f, &r, &g, &b); m_elev[i] = f; m_color[i].Set(r, g, b); } } } fclose(fp); return true; }
/** * Read the TIN body from a native TIN format (.itf) file. You should * first call ReadHeader() if you are doing a two-part read. */ bool vtTin::ReadBody(const char *fname, bool progress_callback(int)) { // first read the point from the .tin file FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; bool success = _ReadTinBody(fp, progress_callback); fclose(fp); if (!success) return false; return true; }
/** * Read the TIN header from a native TIN format (.itf). Reading the header is * quick and lets you query properties (NumVerts, NumTris, GetEarthExtents) * before loading the rest of the file. */ bool vtTin::ReadHeader(const char *fname) { // first read the point from the .tin file FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; bool success = _ReadTinHeader(fp); fclose(fp); if (!success) return false; return true; }
/** * Read a image file into the DIB. This method will check to see if the * file is a BMP or JPEG and call the appropriate reader. */ bool vtDIB::Read(const char *fname, bool progress_callback(int)) { FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; uchar buf[2]; if (fread(buf, 2, 1, fp) != 1) return false; fclose(fp); if (buf[0] == 0x42 && buf[1] == 0x4d) return ReadBMP(fname, progress_callback); else if (buf[0] == 0xFF && buf[1] == 0xD8) return ReadJPEG(fname, progress_callback); else if (buf[0] == 0x89 && buf[1] == 0x50) return ReadPNG(fname, progress_callback); return false; }
// // data is saved in JPEG format // \param quality JPEG quality in the range of 0..100. // bool MiniDatabuf::savedataJPEG(const char *filename, int quality) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; FILE *outfile = vtFileOpen(filename, "wb"); if (outfile == NULL) return false; /* Initialize the JPEG decompression object with default error handling. */ cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); /* Specify data source for decompression */ jpeg_stdio_dest(&cinfo, outfile); // set parameters for compression cinfo.image_width = xsize; /* image width and height, in pixels */ cinfo.image_height = ysize; cinfo.input_components = 3; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ // Now use the library's routine to set default compression parameters. jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); /* Start compressor */ jpeg_start_compress(&cinfo, TRUE); /* Process each scanline */ int row_stride = cinfo.image_width * cinfo.input_components; while (cinfo.next_scanline < cinfo.image_height) { JSAMPROW row_buffer = (JSAMPROW)data + cinfo.next_scanline * row_stride; jpeg_write_scanlines(&cinfo, &row_buffer, 1); } jpeg_finish_compress(&cinfo); /* After finish_compress, we can close the output file. */ fclose(outfile); jpeg_destroy_compress(&cinfo); return true; }
/** * Write the TIN to a Stanford Polygon File Format (PLY), * http://en.wikipedia.org/wiki/PLY_(file_format) */ bool vtTin::WritePLY(const char *fname, bool progress_callback(int)) const { FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; int i, count = 0; int verts = NumVerts(); int tris = NumTris(); int total = verts + tris; fprintf(fp, "ply\n"); fprintf(fp, "format ascii 1.0\n"); fprintf(fp, "comment VTBuilder generated\n"); fprintf(fp, "element vertex %d\n", verts); fprintf(fp, "property float x\n"); fprintf(fp, "property float y\n"); fprintf(fp, "property float z\n"); fprintf(fp, "element face %d\n", tris); fprintf(fp, "property list uchar int vertex_indices\n"); fprintf(fp, "end_header\n"); // write verts for (i = 0; i < verts; i++) { fprintf(fp, "%lf %lf %f\n", m_vert[i].x, m_vert[i].y, m_z[i]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } // write tris for (i = 0; i < tris; i++) { // Here is triangle definition (zero based) A B C ... fprintf(fp, "3 %d %d %d\n", m_tri[i*3+0], m_tri[i*3+1], m_tri[i*3+2]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fclose(fp); return true; }
// // helper - read a LF-delimited DEM and write it with fixed-length records // bool ConvertDLG_from_LFDelim(const char *fname_from, const char *fname_to) { FILE *out = vtFileOpen(fname_to, "wb"); if (!out) return false; char buf[160]; vtDLGFile in; in.Read(fname_from); if (!in.m_iError) { in.OpenFile(); while (in.GetRecord(buf)) { fwrite(buf, 80, 1, out); } in.CloseFile(); } fclose(out); return true; }
/** * Write the TIN to the GMS format. Historically GMS stood for 'Groundwater * Modeling System' from the EMS-I company, now called Aquaveo. */ bool vtTin::WriteGMS(const char *fname, bool progress_callback(int)) const { FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; // first line is file identifier fprintf(fp, "TIN\n"); fprintf(fp, "BEGT\n"); fprintf(fp, "ID 1\n"); // Indices start at 1 //fprintf(fp, "TNAM tin\n"); // "name" of the TIN; optional //fprintf(fp, "MAT 1\n"); // "TIN material ID"; optional int count = 0; const int verts = NumVerts(); const int tris = NumTris(); const int total = verts + tris; // write verts fprintf(fp, "VERT %d\n", verts); for (int i = 0; i < verts; i++) { fprintf(fp, "%lf %lf %f\n", m_vert[i].x, m_vert[i].y, m_z[i]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } // write tris fprintf(fp, "TRI %d\n", tris); for (int i = 0; i < tris; i++) { // the indices in the file are 1-based, so add 1 fprintf(fp, "%d %d %d\n", m_tri[i*3+0]+1, m_tri[i*3+1]+1, m_tri[i*3+2]+1); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, "ENDT\n"); fclose(fp); return true; }
bool ColorMap::Save(const char *fname) const { // watch out for %f LocaleWrap normal_numbers(LC_NUMERIC, "C"); FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; fprintf(fp, "colormap1\n"); fprintf(fp, "blend: %d\n", m_bBlend); fprintf(fp, "relative: %d\n", m_bRelative); int size = m_elev.size(); fprintf(fp, "size %d\n", size); for (int i = 0; i < size; i++) { fprintf(fp, "\telev %f color %d %d %d\n", m_elev[i], m_color[i].r, m_color[i].g, m_color[i].b); } fclose(fp); return true; }
bool Countries::ReadCountryList(const char *fname) { FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; int off; vtString str, name, abb; char buf[400]; while (fgets(buf, 400, fp) != NULL) { str = buf; off = str.Find(':'); Country *country = new Country; country->m_full = str.Left(off); country->m_abb = str.Mid(off+1, 2); m_countries.Append(country); } fclose(fp); return true; }
bool Countries::ReadGCF(const char *fname, bool progress_callback(int)) { FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; int num; fread(&num, sizeof(int), 1, fp); m_countries.SetMaxSize(num); int i, j, num_places; for (i = 0; i < num; i++) { if (progress_callback != NULL) progress_callback(i * 100 / num); Country *country = new Country; m_countries.Append(country); ReadString(fp, country->m_full); printf("Reading %s...\n", (const char *) country->m_full); fread(&num_places, sizeof(int), 1, fp); country->m_places.SetMaxSize(num_places); for (j = 0; j < num_places; j++) { Place *place = new Place; fread(&place->m_pos.x, sizeof(double), 2, fp); ReadString(fp, place->m_fullname_nd); country->m_places.Append(place); } } fclose(fp); return true; }
bool vtDLGFile::Read(const char *fname, bool progress_callback(int)) { char buf[80]; int i, j, iUTMZone; // basic initialization m_iError = 0; m_fname = fname; m_fp = vtFileOpen(fname, "rb"); if (!m_fp) { m_iError = DLG_ERR_FILE; return false; } // check to see if this is a LF-delimited file m_bLFdelimited = false; fseek(m_fp, 56, SEEK_SET); for (i = 0; i < 24; i++) { if (fgetc(m_fp) == 10) m_bLFdelimited = true; } // rewind to beginning fseek(m_fp, 0, SEEK_SET); // record 1 - banner if (!GetRecord(buf)) return false; strncpy(m_header, buf, 72); // record 2 - cell name, date, qualifier, scale, sectional indicator if (!GetRecord(buf)) return false; // record 3 - contour interval info, status flags if (!GetRecord(buf)) return false; // record 4 - codes, resolution, transformation info if (!GetRecord(buf)) return false; int reference_system = geti6(buf + 6); // 1 = UTM, 3 = Albers if (reference_system == 3) // We don't support Albers return false; iUTMZone = geti6(buf + 12); // Datum. Undocumented field! Had to look at the government's // own "dlgv32" source to figure out how to find this value. int iDLGDatum = geti3(buf + 66); // safety check.. because they do if ((iDLGDatum < 0) || (iDLGDatum > 4)) iDLGDatum = 0; // this is how they interpret the value int iDatum; switch (iDLGDatum) { case 0: iDatum = EPSG_DATUM_NAD27; break; case 1: iDatum = EPSG_DATUM_NAD83; break; default: iDatum = -1; break; } // record 5-9 - Projection parameters for map transformation for (i = 5; i < 10; i++) if (!GetRecord(buf)) return false; // record 10 - Internal file-to-map projection transformation parameters if (!GetRecord(buf)) return false; // record 11 - SW quadrangle corner if (!GetRecord(buf)) return false; m_SW_lat.y = getd12(buf+6); m_SW_lat.x = getd12(buf+18); m_SW_utm.x = getd12(buf+36); m_SW_utm.y = getd12(buf+48); // record 12 - NW quadrangle corner if (!GetRecord(buf)) return false; m_NW_lat.y = getd12(buf+6); m_NW_lat.x = getd12(buf+18); m_NW_utm.x = getd12(buf+36); m_NW_utm.y = getd12(buf+48); // record 13 - NE quadrangle corner if (!GetRecord(buf)) return false; m_NE_lat.y = getd12(buf+6); m_NE_lat.x = getd12(buf+18); m_NE_utm.x = getd12(buf+36); m_NE_utm.y = getd12(buf+48); // record 14 - SE quadrangle corner if (!GetRecord(buf)) return false; m_SE_lat.y = getd12(buf+6); m_SE_lat.x = getd12(buf+18); m_SE_utm.x = getd12(buf+36); m_SE_utm.y = getd12(buf+48); // Special exception: DLG for Hawai`i that says "NAD27" is actually in // Old Hawaiian Datum (OHD) - so check for it. if ((iUTMZone == 4 || iUTMZone == 5) && m_SW_utm.y < 3500000) { if (iDatum == EPSG_DATUM_NAD27) iDatum = EPSG_DATUM_OLD_HAWAIIAN; } // We now know enough to set the projection. m_proj.SetProjectionSimple(true, iUTMZone, iDatum); // record 15 - category name, attribute format code, number of nodes... if (!GetRecord(buf)) return false; m_iNodes = geti6(buf + 24); m_iAreas = geti6(buf + 40); m_iLines = geti6(buf + 56); // allocate storage space m_nodes.resize(m_iNodes); m_areas.resize(m_iAreas); m_lines.resize(m_iLines); int total = m_iNodes + m_iAreas + m_iLines, elem = 0; // now read the nodes for (i = 0; i < m_iNodes; i++) { if (!GetRecord(buf)) return false; // do some safety checking if (buf[0] != 'N') break; // make sure node starts with a N int id = geti6(buf + 1); if (id != i+1) break; // got the right node number? m_nodes[i].m_p.x = getd12(buf+6); m_nodes[i].m_p.y = getd12(buf+18); m_nodes[i].m_iAttribs = geti6(buf + 48); int elements = geti6(buf + 36); int extra_records = ((elements*6) + 71) / 72 + (m_nodes[i].m_iAttribs>0); // linkage records for (int e = 0; e < extra_records; e++) if (!GetRecord(buf)) return false; if (progress_callback && (++elem % 20) == 0) progress_callback(elem * 100 / total); } // now read the areas for (i = 0; i < m_iAreas; i++) { if (!GetRecord(buf)) return false; // do some safety checking if (buf[0] != 'A') break; // make sure area starts with a A int id = geti6(buf + 1); if (id != i+1) break; // got the right area number? m_areas[i].m_p.x = getd12(buf+6); m_areas[i].m_p.y = getd12(buf+18); m_areas[i].m_iAttribs = geti6(buf + 48); int elements = geti6(buf + 36); int extra_records = ((elements*6) + 71) / 72 + (m_areas[i].m_iAttribs>0); // linkage records for (int e = 0; e < extra_records; e++) if (!GetRecord(buf)) return false; if (progress_callback && (++elem % 20) == 0) progress_callback(elem * 100 / total); } // now read the lines for (i = 0; i < m_iLines; i++) { if (!GetRecord(buf)) return false; // do some safety checking if (buf[0] != 'L') break; // make sure line starts with a L int id = geti6(buf + 1); if (id != i+1) break; // got the right area number? m_lines[i].m_iNode1 = geti6(buf+6); m_lines[i].m_iNode2 = geti6(buf+12); m_lines[i].m_iLeftArea = geti6(buf+18); m_lines[i].m_iRightArea = geti6(buf+24); m_lines[i].m_iCoords = geti6(buf + 42); m_lines[i].m_iAttribs = geti6(buf + 48); // coordinate records m_lines[i].m_p.SetSize(m_lines[i].m_iCoords); int offset = 0; double x, y; for (int c = 0; c < m_lines[i].m_iCoords; c++) { if (c%3 == 0) { if (!GetRecord(buf)) return false; offset = 0; } x = getd12(buf+offset); offset += 12; y = getd12(buf+offset); offset += 12; m_lines[i].m_p[c].x = x; m_lines[i].m_p[c].y = y; } // attribute records if (m_lines[i].m_iAttribs) { m_lines[i].m_attr.resize(m_lines[i].m_iAttribs); for (j = 0; j < m_lines[i].m_iAttribs; j++) { if (j%6 == 0) { if (!GetRecord(buf)) return false; offset = 0; } m_lines[i].m_attr[j].m_iMajorAttr = geti6(buf+offset); offset += 6; m_lines[i].m_attr[j].m_iMinorAttr = geti6(buf+offset); offset += 6; } } if (progress_callback && (++elem % 20) == 0) progress_callback(elem * 100 / total); } // all done, close up fclose(m_fp); m_fp = NULL; return true; }
void vtDLGFile::OpenFile() { m_fp = vtFileOpen(m_fname, "rb"); }
/** * Loads elevation from a USGS DEM file. * * Some non-standard variations of the DEM format are supported. * * You should call SetupLocalCS() after loading if you will be doing * heightfield operations on this grid. * * \returns \c true if the file was successfully opened and read. */ bool vtElevationGrid::LoadFromDEM(const char *szFileName, bool progress_callback(int), vtElevError *err) { // Free buffers to prepare to receive new data FreeData(); if (progress_callback != NULL) progress_callback(0); FILE *fp = vtFileOpen(szFileName,"rb"); if (!fp) // Cannot Open File { SetError(err, vtElevError::FILE_OPEN, "Couldn't open file '%s'", szFileName); return false; } // check for version of DEM format int iRow, iColumn; char buffer[158]; fseek(fp, 864, 0); if (fread(buffer, 144, 1, fp) != 1) { SetError(err, vtElevError::READ_DATA, "Couldn't read DEM data from '%s'", szFileName); return false; } bool bOldFormat = (strncmp(buffer, " 1 1", 12) == 0); bool bNewFormat = false; bool bFixedLength = true; int iDataStartOffset = 1024; // set here to avoid compiler warning int i, j; if (bOldFormat) iDataStartOffset = 1024; // 1024 is record length else { fseek(fp, 1024, 0); // Check for New Format IConvert(fp, 6, iRow); IConvert(fp, 6, iColumn); if (iRow==1 && iColumn==1) // File OK? { bNewFormat = true; iDataStartOffset = 1024; } else { // might be the Non-fixed-length record format // Record B can start anywhere from 865 to 1023 // Record B is identified by starting with the row/column // of its first profile, " 1 1" fseek(fp, 865, 0); if (fread(buffer, 158, 1, fp) != 1) { SetError(err, vtElevError::READ_DATA, "Couldn't read DEM data from '%s'", szFileName); fclose(fp); return false; } for (i = 0; i < 158-12; i++) { if (!strncmp(buffer+i, " 1 1", 12)) { // Found it bFixedLength = false; iDataStartOffset = 865+i; break; } } if (i == 158-12) { // Not a DEM file SetError(err, vtElevError::READ_DATA, "Couldn't read DEM data from '%s'", szFileName); fclose(fp); return false; } } } // Read the embedded DEM name char szName[41]; fseek(fp, 0, 0); if (fgets(szName, 41, fp) == NULL) return false; int len = strlen(szName); // trim trailing whitespace while (len > 0 && szName[len-1] == ' ') { szName[len-1] = 0; len--; } m_strOriginalDEMName = szName; fseek(fp, 156, 0); int iCoordSystem, iUTMZone; IConvert(fp, 6, iCoordSystem); IConvert(fp, 6, iUTMZone); fseek(fp, 168, 0); double dProjParams[15]; for (i = 0; i < 15; i++) { if (!DConvert(fp, 24, dProjParams[i], DEBUG_DEM)) return false; } int iDatum = EPSG_DATUM_NAD27; // default // OLD format header ends at byte 864 (0x360); new format has Datum if (bNewFormat) { // year of data compilation char szDateBuffer[5]; fseek(fp, 876, 0); // 0x36C if (fread(szDateBuffer, 4, 1, fp) != 1) return false; szDateBuffer[4] = 0; // Horizontal datum // 1=North American Datum 1927 (NAD 27) // 2=World Geodetic System 1972 (WGS 72) // 3=WGS 84 // 4=NAD 83 // 5=Old Hawaii Datum // 6=Puerto Rico Datum fseek(fp, 890, 0); // 0x37A int datum; IConvert(fp, 2, datum); VTLOG("DEM Reader: Read Datum Value %d\n", datum); switch (datum) { case 1: iDatum = EPSG_DATUM_NAD27; break; case 2: iDatum = EPSG_DATUM_WGS72; break; case 3: iDatum = EPSG_DATUM_WGS84; break; case 4: iDatum = EPSG_DATUM_NAD83; break; case 5: iDatum = EPSG_DATUM_OLD_HAWAIIAN; break; case 6: iDatum = EPSG_DATUM_PUERTO_RICO; break; } } fseek(fp, 528, 0); int iGUnit, iVUnit; IConvert(fp, 6, iGUnit); IConvert(fp, 6, iVUnit); // Ground (Horizontal) Units in meters double fGMeters; switch (iGUnit) { case 0: fGMeters = 1.0; break; // 0 = radians (never encountered) case 1: fGMeters = 0.3048; break; // 1 = feet case 2: fGMeters = 1.0; break; // 2 = meters case 3: fGMeters = 30.922; break; // 3 = arc-seconds } // Vertical Units in meters double fVertUnits; switch (iVUnit) { case 1: fVertUnits = 0.3048; break; // feet to meter conversion case 2: fVertUnits = 1.0; break; // meters == meters default: fVertUnits = 1.0; break; // anything else, assume meters } fseek(fp, 816, 0); double dxdelta, dydelta, dzdelta; DConvert(fp, 12, dxdelta, DEBUG_DEM); // dxdelta (unused) DConvert(fp, 12, dydelta, DEBUG_DEM); DConvert(fp, 12, dzdelta, DEBUG_DEM); m_bFloatMode = false; // Read the coordinates of the 4 corners VTLOG("DEM corners:\n"); DPoint2 corners[4]; // SW, NW, NE, SE fseek(fp, 546, 0); for (i = 0; i < 4; i++) { DConvert(fp, 24, corners[i].x, DEBUG_DEM); DConvert(fp, 24, corners[i].y, DEBUG_DEM); } for (i = 0; i < 4; i++) VTLOG(" (%lf, %lf)", corners[i].x, corners[i].y); VTLOG("\n"); // Set up the projection and corners bool bGeographic = (iCoordSystem == 0); if (bGeographic) { for (i = 0; i < 4; i++) { // convert arcseconds to degrees m_Corners[i].x = corners[i].x / 3600.0; m_Corners[i].y = corners[i].y / 3600.0; } } else { // some linear coordinate system for (i = 0; i < 4; i++) m_Corners[i] = corners[i]; } // Special case. Some old DEMs claim to be NAD27, but they are of Hawai'i, // and Hawai'i has no NAD27, it is actually OHD. if (iDatum == EPSG_DATUM_NAD27) { DRECT Hawaii(0,0,0,0); if (bGeographic) Hawaii.SetRect(-164, 24, -152, 17); else if (iCoordSystem == 1) // UTM { if (iUTMZone == 4) Hawaii.SetRect(240000, 2600000, 1000000, 2000000); else if (iUTMZone == 5) Hawaii.SetRect(-400000, 2600000, 400000, 2000000); } for (i = 0; i < 4; i++) { if (Hawaii.ContainsPoint(m_Corners[i])) iDatum = EPSG_DATUM_OLD_HAWAIIAN; } } bool bSuccessfulCRS = true; switch (iCoordSystem) { case 0: // geographic (lat-lon) iUTMZone = -1; bSuccessfulCRS = m_proj.SetProjectionSimple(false, iUTMZone, iDatum); break; case 1: // utm m_proj.SetProjectionSimple(true, iUTMZone, iDatum); break; case 3: // Albers Conical Equal Area { // The Official DEM documentation says: // "Note: All angles (latitudes, longitudes, or azimuth) are // required in degrees, minutes, and arc seconds in the packed // real number format +DDDOMMOSS.SSSSS." // However, what i've actually seen is values like: // 0.420000000000000D+06' -> 420000 // for 42 degrees, which is off by a decimal point from the DEM docs. // So, intepret the values with factor of 10000 to convert to degrees: // double semi_major = dProjParams[0]; // unused // double eccentricity = dProjParams[1]; // unused double lat_1st_std_parallel = dProjParams[2] / 10000; double lat_2nd_std_parallel = dProjParams[3] / 10000; double lon_central_meridian = dProjParams[4] / 10000; double lat_origin = dProjParams[5] / 10000; double false_easting = dProjParams[6]; double false_northing = dProjParams[7]; m_proj.SetGeogCSFromDatum(iDatum); m_proj.SetACEA(lat_1st_std_parallel, lat_2nd_std_parallel, lat_origin, lon_central_meridian, false_easting, false_northing); } break; case 2: // State Plane (!) case 4: // Lambert Conformal case 5: // Mercator case 6: // Polar Stereographic case 7: // Polyconic case 8: // Equidistant Conic Type A / B case 9: // Transverse Mercator case 10: // Stereographic case 11: // Lambert Azimuthal Equal-Area case 12: // Azimuthal Equidistant case 13: // Gnomonic case 14: // Orthographic case 15: // General Vertical Near-Side Perspective case 16: // Sinusoidal (Plate Caree) case 17: // Equirectangular case 18: // Miller Cylindrical case 19: // Van Der Grinten I case 20: // Oblique Mercator VTLOG("Warning! We don't yet support DEM coordinate system %d.\n", iCoordSystem); break; } // We must have a functional CRS, or it will sebsequently fail if (!bSuccessfulCRS) { SetError(err, vtElevError::READ_CRS, "Couldn't determine CRS of DEM file"); return false; } double dElevMin, dElevMax; DConvert(fp, 24, dElevMin, DEBUG_DEM); DConvert(fp, 24, dElevMax, DEBUG_DEM); fseek(fp, 852, 0); int iRows, iProfiles; IConvert(fp, 6, iRows); // This "Rows" value will always be 1 IConvert(fp, 6, iProfiles); VTLOG("DEM profiles: %d\n", iProfiles); m_iSize.x = iProfiles; // values we'll need while scanning the elevation profiles int iProfileRows, iProfileCols; int iElev; double dLocalDatumElev, dProfileMin, dProfileMax; int ygap; double dMinY; DPoint2 start; if (bGeographic) { // If it's in degrees, it's flush square, so we can simply // derive the extents (m_EarthExtents) from the quad corners (m_Corners) ComputeExtentsFromCorners(); dMinY = std::min(corners[0].y, corners[3].y); } else { VTLOG("DEM scanning to compute extents\n"); m_EarthExtents.SetInsideOut(); if (!bFixedLength) fseek(fp, iDataStartOffset, 0); // Need to scan over all the profiles, accumulating the TRUE // extents of the actual data points. int record = 0; int data_len; for (i = 0; i < iProfiles; i++) { if (progress_callback != NULL) progress_callback(i*49/iProfiles); if (bFixedLength) fseek(fp, iDataStartOffset + (record * 1024), 0); // We cannot use IConvert here, because there *might* be a spurious LF // after the number - seen in some rare files. if (fscanf(fp, "%d", &iRow) != 1) { SetError(err, vtElevError::READ_DATA, "Error reading DEM at profile %d of %d", i, iProfiles); return false; } IConvert(fp, 6, iColumn); // assert(iColumn == i+1); IConvert(fp, 6, iProfileRows); IConvert(fp, 6, iProfileCols); DConvert(fp, 24, start.x); DConvert(fp, 24, start.y); m_EarthExtents.GrowToContainPoint(start); start.y += ((iProfileRows-1) * dydelta); m_EarthExtents.GrowToContainPoint(start); if (bFixedLength) { record++; data_len = 144 + (iProfileRows * 6); while (data_len > 1020) // max bytes in a record { data_len -= 1020; record++; } } else { DConvert(fp, 24, dLocalDatumElev); DConvert(fp, 24, dProfileMin); DConvert(fp, 24, dProfileMax); for (j = 0; j < iProfileRows; j++) { // We cannot use IConvert here, because there *might* be a spurious LF // after the number - seen in some rare files. if (fscanf(fp, "%d", &iElev) != 1) return false; } } } dMinY = m_EarthExtents.bottom; } VTLOG("DEM extents LRTB: %lf, %lf, %lf, %lf\n", m_EarthExtents.left, m_EarthExtents.right, m_EarthExtents.top, m_EarthExtents.bottom); // Compute number of rows double fRows; if (bGeographic) { // degrees fRows = m_EarthExtents.Height() / dydelta * 3600.0f; m_iSize.y = (int)fRows + 1; // 1 more than quad spacing } else { // some linear coordinate system fRows = m_EarthExtents.Height() / dydelta; m_iSize.y = (int)(fRows + 0.5) + 1; // round to the nearest integer } // safety check if (m_iSize.y > 20000) return false; if (!AllocateGrid(err)) return false; // jump to start of actual data fseek(fp, iDataStartOffset, 0); for (i = 0; i < iProfiles; i++) { if (progress_callback != NULL) progress_callback(50+i*49/iProfiles); // We cannot use IConvert here, because there *might* be a spurious LF // after the number - seen in some rare files. if (fscanf(fp, "%d", &iRow) != 1) return false; IConvert(fp, 6, iColumn); //assert(iColumn == i+1); IConvert(fp, 6, iProfileRows); IConvert(fp, 6, iProfileCols); DConvert(fp, 24, start.x); DConvert(fp, 24, start.y); DConvert(fp, 24, dLocalDatumElev); DConvert(fp, 24, dProfileMin); DConvert(fp, 24, dProfileMax); ygap = (int)((start.y - dMinY)/dydelta); for (j = ygap; j < (ygap + iProfileRows); j++) { //assert(j >=0 && j < m_iSize.y); // useful safety check // We cannot use IConvert here, because there *might* be a spurious LF // after the number - seen in some rare files. if (fscanf(fp, "%d", &iElev) != 1) return false; if (iElev == -32767 || iElev == -32768) SetValue(i, j, INVALID_ELEVATION); else { // The DEM spec says: // "A value in this array would be multiplied by the "z" spatial // resolution (data element 15, record type A) and added to the // "Elevation of local datum for the profile" (data element 4, record // type B) to obtain the elevation for the point." SetValue(i, j, (short) iElev + (short) dLocalDatumElev); } } } fclose(fp); m_fVMeters = (float) (fVertUnits * dzdelta); ComputeHeightExtents(); if (m_fMinHeight != dElevMin || m_fMaxHeight != dElevMax) VTLOG("DEM Reader: elevation extents in .dem (%.1f, %.1f) don't match data (%.1f, %.1f).\n", dElevMin, dElevMax, m_fMinHeight, m_fMaxHeight); return true; }
int vtUnzip::Extract(bool bFullPath, bool bOverwrite, const char *lpszDst, bool progress_callback(int)) { int iCount = 0; int iTotal = GetGlobalCount(); bool bOK = true; for (bool bContinue = GoToFirstFile(); bContinue; bContinue = GoToNextFile()) { char szFileName[MAX_PATH]; unz_file_info info; bOK = GetCurrentFileInfo(&info, szFileName, MAX_PATH); if (!bOK) break; vtString src_filename = szFileName; const char *short_fname = (const char *)src_filename; for (const char *p = short_fname; (*p) != '\0'; p++) { if (((*p)=='/') || ((*p)=='\\')) { short_fname = p+1; } } vtString short_filename = short_fname; if ((*short_filename)=='\0') { if (bFullPath) { VTLOG("creating directory: %s\n", (const char *)src_filename); vtCreateDir(src_filename); } } else { bOK = OpenCurrentFile(); if (bOK) { vtString write_filename; write_filename = vtString(lpszDst) + (bFullPath ? src_filename : short_filename); vtString strResult; VTLOG("Extracting %s ...", (const char *) write_filename); if (ExtractAccept(write_filename, bOverwrite)) { FILE* file = vtFileOpen(write_filename, "wb"); bool bWrite = (file != NULL); if (bWrite) { char buf[4096]; bWrite = ExtractCurrentFile(file, buf, 4096); fclose(file); } if (bWrite) { vtUnzip::change_file_date(write_filename,info.dosDate, info.tmu_date); iCount++; if (progress_callback != NULL) { progress_callback(iCount * 99 / iTotal); } } else { m_error_count++; OnError(write_filename); } strResult = bWrite ? "ok" : "failed"; } else { strResult = "skipped"; } VTLOG(" %s\n", (const char *) strResult); CloseCurrentFile(); } } } if (bOK) return iCount; else return -1; }
/** * Writes the grid to a BT (Binary Terrain) file. * The current BT format version (1.3) is written. * * \param szFileName The file name to write to. * \param progress_callback If supplied, this function will be called back * with a value of 0 to 100 as the operation progresses. * \param bGZip If true, the data will be compressed with gzip. * If true, you should Use a filename ending with ".gz". */ bool vtElevationGrid::SaveToBT(const char *szFileName, bool progress_callback(int), bool bGZip) { int w = m_iColumns; int h = m_iRows; short zone = (short) m_proj.GetUTMZone(); short datum = (short) m_proj.GetDatum(); short isfloat = (short) IsFloatMode(); short external = 1; // always true: we always write an external .prj file LinearUnits units = m_proj.GetUnits(); int hunits = (int) units; // Latest header, version 1.2 short datasize = m_bFloatMode ? 4 : 2; DataType datatype = m_bFloatMode ? DT_FLOAT : DT_SHORT; if (bGZip == false) { // Use conventional IO FILE *fp = vtFileOpen(szFileName, "wb"); if (!fp) return false; fwrite("binterr1.3", 10, 1, fp); FWrite(&w, DT_INT, 1, fp, BO_LITTLE_ENDIAN); FWrite(&h, DT_INT, 1, fp, BO_LITTLE_ENDIAN); FWrite(&datasize, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); FWrite(&isfloat, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); FWrite(&hunits, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // Horizontal Units (0, 1, 2, 3) FWrite(&zone, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // UTM zone FWrite(&datum, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // Datum // coordinate extents FWrite(&m_EarthExtents.left, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); FWrite(&m_EarthExtents.right, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); FWrite(&m_EarthExtents.bottom, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); FWrite(&m_EarthExtents.top, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); FWrite(&external, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // External projection specification FWrite(&m_fVMeters, DT_FLOAT, 1, fp, BO_LITTLE_ENDIAN); // Vertical scale factor (meters/units) // now write the data: always starts at offset 256 fseek(fp, 256, SEEK_SET); #if 0 // slow way, one heixel at a time for (int i = 0; i < w; i++) { if (progress_callback != NULL) progress_callback(i * 100 / w); for (j = 0; j < h; j++) { if (m_bFloatMode) { fvalue = GetFValue(i, j); FWrite(&fvalue, datatype, 1, fp, BO_LITTLE_ENDIAN); } else { svalue = GetValue(i, j); FWrite(&svalue, datatype, 1, fp, BO_LITTLE_ENDIAN); } } } #else // fast way, with the assumption that the data is stored column-first in memory if (m_bFloatMode) { for (int i = 0; i < w; i++) { if (progress_callback != NULL) { if (progress_callback(i * 100 / w)) { fclose(fp); return false; } } FWrite(m_pFData + (i * m_iRows), DT_FLOAT, m_iRows, fp, BO_LITTLE_ENDIAN); } } else { for (int i = 0; i < w; i++) { if (progress_callback != NULL) { if (progress_callback(i * 100 / w)) { fclose(fp); return false; } } FWrite(m_pData + (i * m_iRows), DT_SHORT, m_iRows, fp, BO_LITTLE_ENDIAN); } } #endif fclose(fp); } else { // Use GZip IO gzFile fp = vtGZOpen(szFileName, "wb"); if (!fp) return false; gzwrite(fp, (void *)"binterr1.3", 10); GZFWrite(&w, DT_INT, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&h, DT_INT, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&datasize, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&isfloat, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&hunits, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // Horizontal Units (0, 1, 2, 3) GZFWrite(&zone, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // UTM zone GZFWrite(&datum, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // Datum // coordinate extents GZFWrite(&m_EarthExtents.left, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&m_EarthExtents.right, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&m_EarthExtents.bottom, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&m_EarthExtents.top, DT_DOUBLE, 1, fp, BO_LITTLE_ENDIAN); GZFWrite(&external, DT_SHORT, 1, fp, BO_LITTLE_ENDIAN); // External projection specification GZFWrite(&m_fVMeters, DT_FLOAT, 1, fp, BO_LITTLE_ENDIAN); // Vertical scale factor (meters/units) // now write the data: always starts at offset 256 gzseek(fp, 256, SEEK_SET); // fast way, with the assumption that the data is stored column-first in memory if (m_bFloatMode) { for (int i = 0; i < w; i++) { if (progress_callback != NULL) { if (progress_callback(i * 100 / w)) { gzclose(fp); return false; } } GZFWrite(m_pFData + (i * m_iRows), DT_FLOAT, m_iRows, fp, BO_LITTLE_ENDIAN); } } else { for (int i = 0; i < w; i++) { if (progress_callback != NULL) { if (progress_callback(i * 100 / w)) { gzclose(fp); return false; } } GZFWrite(m_pData + (i * m_iRows), DT_SHORT, m_iRows, fp, BO_LITTLE_ENDIAN); } } gzclose(fp); } if (external) { // Write external projection file (.prj) char prj_name[256]; strcpy(prj_name, szFileName); int len = strlen(prj_name); if (bGZip) strcpy(prj_name + len - 6, ".prj"); // overwrite the .bt.gz else strcpy(prj_name + len - 3, ".prj"); // overwrite the .bt m_proj.WriteProjFile(prj_name); } return true; }
bool vtImage::_ReadPNG(const char *filename) { FILE *fp = NULL; uchar header[8]; png_structp png; png_infop info; png_infop endinfo; png_bytep *row_p; png_uint_32 width, height; int depth, color; png_uint_32 i; png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png) { // We compiled against the headers of one version of libpng, but // linked against the libraries from another version. If you get // this, fix the paths in your development environment. return false; } info = png_create_info_struct(png); endinfo = png_create_info_struct(png); fp = vtFileOpen(filename, "rb"); if (fp && fread(header, 1, 8, fp) && png_check_sig(header, 8)) png_init_io(png, fp); else { png_destroy_read_struct(&png, &info, &endinfo); return false; } png_set_sig_bytes(png, 8); png_read_info(png, info); png_get_IHDR(png, info, &width, &height, &depth, &color, NULL, NULL, NULL); if (color == PNG_COLOR_TYPE_GRAY || color == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); // never strip alpha // { // png_set_strip_alpha(png); // color &= ~PNG_COLOR_MASK_ALPHA; // } // Always expand paletted images if (color == PNG_COLOR_TYPE_PALETTE) png_set_expand(png); /*--GAMMA--*/ // checkForGammaEnv(); double screenGamma = 2.2 / 1.0; #if 0 // Getting the gamma from the PNG file is disabled here, since // PhotoShop writes bizarre gamma values like .227 (PhotoShop 5.0) // or .45 (newer versions) double fileGamma; if (png_get_gAMA(png, info, &fileGamma)) png_set_gamma(png, screenGamma, fileGamma); else #endif png_set_gamma(png, screenGamma, 1.0/2.2); png_read_update_info(png, info); m_pPngData = (png_bytep) malloc(png_get_rowbytes(png, info)*height); row_p = (png_bytep *) malloc(sizeof(png_bytep)*height); bool StandardOrientation = true; for (i = 0; i < height; i++) { if (StandardOrientation) row_p[height - 1 - i] = &m_pPngData[png_get_rowbytes(png, info)*i]; else row_p[i] = &m_pPngData[png_get_rowbytes(png, info)*i]; } png_read_image(png, row_p); free(row_p); int iBitCount; switch (color) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_PALETTE: iBitCount = 24; break; case PNG_COLOR_TYPE_GRAY_ALPHA: case PNG_COLOR_TYPE_RGB_ALPHA: iBitCount = 32; break; default: return false; } png_read_end(png, endinfo); png_destroy_read_struct(&png, &info, &endinfo); // Don't free the data, we're going to pass it to OSG // free(m_pPngData); if (fp) fclose(fp); int pixelFormat; uint internalFormat; if (iBitCount == 24) pixelFormat = GL_RGB; else if (iBitCount == 32) pixelFormat = GL_RGBA; if (m_internalformat == -1) internalFormat = pixelFormat; // use default else internalFormat = m_internalformat; // use specific setImage(width, height, 1, internalFormat, // int internalFormat, pixelFormat, // uint pixelFormat GL_UNSIGNED_BYTE, // uint dataType m_pPngData, osg::Image::USE_MALLOC_FREE); return true; }
bool vtTin::ReadADF(const char *fname, bool progress_callback(int)) { const vtString tnxy_name = fname; if (tnxy_name.Right(6) != "xy.adf") return false; vtString base = tnxy_name.Left(tnxy_name.GetLength()-6); vtString tnz_name = base + "z.adf"; vtString tnod_name = base + "od.adf"; FILE *fp1 = vtFileOpen(tnxy_name, "rb"); FILE *fp2 = vtFileOpen(tnz_name, "rb"); FILE *fp3 = vtFileOpen(tnod_name, "rb"); if (!fp1 || !fp2 || !fp3) return false; fseek(fp1, 0, SEEK_END); const int length_xy = ftell(fp1); rewind(fp1); // go back again uint num_points = length_xy / 16; // X and Y, each 8 byte doubles fseek(fp2, 0, SEEK_END); const int length_z = ftell(fp2); rewind(fp2); // go back again uint num_heights = length_z / 4; // Z is a 4 byte float DPoint2 p; float z; for (uint i = 0; i < num_points; i++) { if ((i%200) == 0 && progress_callback != NULL) progress_callback(i * 40 / num_points); FRead(&p.x, DT_DOUBLE, 2, fp1, BO_BIG_ENDIAN, BO_LITTLE_ENDIAN); FRead(&z, DT_FLOAT, 1, fp2, BO_BIG_ENDIAN, BO_LITTLE_ENDIAN); AddVert(p, z); } fseek(fp3, 0, SEEK_END); const int length_od = ftell(fp3); rewind(fp3); // go back again const uint num_faces = length_od / 12; // A B C as 4-byte ints int v[3]; for (uint i = 0; i < num_faces; i++) { if ((i%200) == 0 && progress_callback != NULL) progress_callback(40 + i * 40 / num_faces); FRead(v, DT_INT, 3, fp3, BO_BIG_ENDIAN, BO_LITTLE_ENDIAN); AddTri(v[0]-1, v[1]-1, v[2]-1); } fclose(fp1); fclose(fp2); fclose(fp3); // Cleanup: the ESRI TIN contains four "boundary" point far outside the // extents (directly North, South, East, and West). We should ignore // those four points and the triangles connected to them. // It seems we can assume the four 'extra' vertices are the first four. m_vert.RemoveAt(0, 4); m_z.erase(m_z.begin(), m_z.begin() + 4); m_vert_normal.RemoveAt(0, 4); // Re-index the triangles uint total = m_tri.size()/3; for (uint i = 0; i < total; i++) { if ((i%200) == 0 && progress_callback != NULL) progress_callback(80 + i * 20 / total); // Remove any triangles which referenced this vertex if (m_tri[i*3 + 0] < 4 || m_tri[i*3 + 1] < 4 || m_tri[i*3 + 2] < 4) { m_tri.erase(m_tri.begin() + i*3, m_tri.begin() + i*3 + 3); i--; total--; continue; } } // For all other triangles, adjust the indices to reflect the removal for (uint i = 0; i < m_tri.size(); i++) m_tri[i] = m_tri[i] - 4; // Test each triangle for clockwisdom, fix if needed CleanupClockwisdom(); ComputeExtents(); return true; }
/** * Write the TIN to a VRML (.wrl) file as an IndexedFaceSet. Note that we * write X and Y as geographic coordinates, but VRML only supports * single-precision floating point values, so it may lose some precision. */ bool vtTin::WriteWRL(const char *fname, bool progress_callback(int)) const { FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; fprintf(fp, "#VRML V2.0 utf8\n"); fprintf(fp, "\n"); fprintf(fp, "WorldInfo\n"); fprintf(fp, " {\n"); fprintf(fp, " info\n"); fprintf(fp, " [\n"); fprintf(fp, " \"Generated by VTBuilder\"\n"); fprintf(fp, " ]\n"); fprintf(fp, " title \"TIN VRML Model\"\n"); fprintf(fp, " }\n"); fprintf(fp, "\n"); fprintf(fp, "# TIN---------\n"); fprintf(fp, "Transform\n"); fprintf(fp, " {\n"); fprintf(fp, " children\n"); fprintf(fp, " [\n"); fprintf(fp, " Shape\n"); fprintf(fp, " {\n"); fprintf(fp, " appearance Appearance\n"); fprintf(fp, " {\n"); fprintf(fp, " material Material\n"); fprintf(fp, " {\n"); fprintf(fp, " }\n"); fprintf(fp, " texture ImageTexture\n"); fprintf(fp, " {\n"); fprintf(fp, " url\n"); fprintf(fp, " [\n"); fprintf(fp, " \"OrtoImage.jpg\"\n"); fprintf(fp, " ]\n"); fprintf(fp, " }\n"); fprintf(fp, " }\n"); fprintf(fp, " geometry IndexedFaceSet {\n"); fprintf(fp, " ccw FALSE\n"); fprintf(fp, " solid FALSE\n"); fprintf(fp, " creaseAngle 1.396263\n"); fprintf(fp, "coord DEF Kxzy Coordinate {\n"); fprintf(fp, " point [\n"); int i, count = 0; const int verts = NumVerts(); const int tris = NumTris(); const int total = verts + tris; // write verts // fprintf(fp, "VERT %d\n", verts); for (i = 0; i < verts; i++) { fprintf(fp, "%lf %lf %f\n", m_vert[i].x, m_vert[i].y, m_z[i]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, " ]\n"); fprintf(fp, " }\n"); fprintf(fp, " coordIndex \n"); fprintf(fp, " [\n"); // write tris for (i = 0; i < tris; i++) { // Here is triangle definition (zero based) A B C -1... // the indices in the file are 1-based, so add 1 fprintf(fp, "%d %d %d -1\n", m_tri[i*3+0], m_tri[i*3+1], m_tri[i*3+2]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, " ]\n"); fprintf(fp, " }\n"); fprintf(fp, " }\n"); fprintf(fp, " ]\n"); fprintf(fp, " }\n"); fclose(fp); return true; }
/** * Write the TIN to a Collada (.dae) file. Note that we write X and Y as * geographic coordinates, but DAE only supports single-precision floating * point values, so it may lose some precision. */ bool vtTin::WriteDAE(const char *fname, bool progress_callback(int)) const { FILE *fp = vtFileOpen(fname, "wb"); if (!fp) return false; // first line is file identifier fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"); fprintf(fp, "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">\n"); fprintf(fp, " <asset>\n"); fprintf(fp, " <contributor>\n"); fprintf(fp, " <authoring_tool>VTBuilder</authoring_tool>\n"); fprintf(fp, " </contributor>\n"); // fprintf(fp, " <created>2012-01-09T14:26:45Z</created>\n"); // fprintf(fp, " <modified>2012-01-09T14:26:45Z</modified>\n"); // fprintf(fp, " <unit meter=\"0.02539999969303608\" name=\"inch\" />\n"); fprintf(fp, " <up_axis>Z_UP</up_axis>\n"); fprintf(fp, " </asset>\n"); fprintf(fp, " <library_visual_scenes>\n"); fprintf(fp, " <visual_scene id=\"ID1\">\n"); fprintf(fp, " <node name=\"VTBuilder\">\n"); fprintf(fp, " <node id=\"ID2\" name=\"Earth_Terrain\">\n"); fprintf(fp, " <matrix>1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1</matrix>\n"); fprintf(fp, " <instance_geometry url=\"#ID3\">\n"); fprintf(fp, " <bind_material>\n"); fprintf(fp, " <technique_common>\n"); fprintf(fp, " <instance_material symbol=\"Material2\" target=\"#ID4\">\n"); fprintf(fp, " <bind_vertex_input semantic=\"UVSET0\" input_semantic=\"TEXCOORD\" input_set=\"0\" />\n"); fprintf(fp, " </instance_material>\n"); fprintf(fp, " </technique_common>\n"); fprintf(fp, " </bind_material>\n"); fprintf(fp, " </instance_geometry>\n"); fprintf(fp, " </node>\n"); fprintf(fp, " </node>\n"); fprintf(fp, " </visual_scene>\n"); fprintf(fp, " </library_visual_scenes>\n"); fprintf(fp, " <library_geometries>\n"); fprintf(fp, " <geometry id=\"ID3\">\n"); fprintf(fp, " <mesh>\n"); fprintf(fp, " <source id=\"ID6\">\n"); int count = 0; const int verts = NumVerts(); const int tris = NumTris(); const int total = verts + tris; // Here are: Count and Coordinates X Y Z... fprintf(fp, " <float_array id=\"ID10\" count=\"%d\">\n",verts); // write verts for (int i = 0; i < verts; i++) { fprintf(fp, "%lf %lf %f\n", m_vert[i].x, m_vert[i].y, m_z[i]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, " </float_array>\n"); fprintf(fp, " <technique_common>\n"); fprintf(fp, " <accessor count=\"222\" source=\"#ID10\" stride=\"3\">\n"); fprintf(fp, " <param name=\"X\" type=\"float\" />\n"); fprintf(fp, " <param name=\"Y\" type=\"float\" />\n"); fprintf(fp, " <param name=\"Z\" type=\"float\" />\n"); fprintf(fp, " </accessor>\n"); fprintf(fp, " </technique_common>\n"); fprintf(fp, " </source>\n"); fprintf(fp, "\n"); fprintf(fp, " <source id=\"ID8\">\n"); fprintf(fp, " <Name_array id=\"ID12\" count=\"0\" />\n"); fprintf(fp, " <technique_common>\n"); fprintf(fp, " <accessor count=\"0\" source=\"#ID12\" stride=\"1\">\n"); fprintf(fp, " <param name=\"skp_material\" type=\"Name\" />\n"); fprintf(fp, " </accessor>\n"); fprintf(fp, " </technique_common>\n"); fprintf(fp, " </source>\n"); fprintf(fp, " <vertices id=\"ID9\">\n"); fprintf(fp, " <input semantic=\"POSITION\" source=\"#ID6\" />\n"); fprintf(fp, " <input semantic=\"NORMAL\" source=\"#ID7\" />\n"); fprintf(fp, " </vertices>\n"); // Here is triangles Count fprintf(fp, " <triangles count=\"%d\" material=\"Material2\">\n", tris); fprintf(fp, " <input offset=\"0\" semantic=\"VERTEX\" source=\"#ID9\" />\n"); fprintf(fp, " <p>\n"); // write tris // fprintf(fp, "TRI %d\n", tris); for (int i = 0; i < tris; i++) { // Here is triangle definition (zero based) A B C ... // the indices in the file are 1-based, so add 1 fprintf(fp, "%d %d %d\n", m_tri[i*3+0], m_tri[i*3+1], m_tri[i*3+2]); if (progress_callback && (++count % 200) == 0) progress_callback(count * 99 / total); } fprintf(fp, "</p>\n"); fprintf(fp, " </triangles>\n"); fprintf(fp, " </mesh>\n"); fprintf(fp, " </geometry>\n"); fprintf(fp, " </library_geometries>\n"); fprintf(fp, " <library_materials>\n"); fprintf(fp, " <material id=\"ID4\" name=\"Google_Earth_Snapshot\">\n"); fprintf(fp, " <instance_effect url=\"#ID5\" />\n"); fprintf(fp, " </material>\n"); fprintf(fp, " </library_materials>\n"); fprintf(fp, " <library_effects>\n"); fprintf(fp, " <effect id=\"ID5\">\n"); fprintf(fp, " <profile_COMMON>\n"); fprintf(fp, " <technique sid=\"COMMON\">\n"); fprintf(fp, " <lambert>\n"); fprintf(fp, " <diffuse>\n"); // Here is the color definition of the surface fprintf(fp, " <color>0.3411764705882353 0.392156862745098 0.3411764705882353 1</color>\n"); fprintf(fp, " </diffuse>\n"); fprintf(fp, " </lambert>\n"); fprintf(fp, " </technique>\n"); fprintf(fp, " </profile_COMMON>\n"); fprintf(fp, " </effect>\n"); fprintf(fp, " </library_effects>\n"); fprintf(fp, " <scene>\n"); fprintf(fp, " <instance_visual_scene url=\"#ID1\" />\n"); fprintf(fp, " </scene>\n"); fprintf(fp, "</COLLADA>\n"); fclose(fp); return true; }
/** * Write the TIN to the Aquaveo GMS format. */ bool vtTin::ReadGMS(const char *fname, bool progress_callback(int)) { FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; char buf[256]; vtString tin_name; int material_id; int num_points; // first line is file identifier if (fgets(buf, 256, fp) == NULL) return false; if (strncmp(buf, "TIN", 3) != 0) return false; while (1) { if (fgets(buf, 256, fp) == NULL) break; // trim trailing EOL characters vtString vstr = buf; vstr.Remove('\r'); vstr.Remove('\n'); const char *str = (const char *)vstr; if (!strncmp(str, "BEGT", 4)) // beginning of TIN block continue; if (!strncmp(str, "ID", 2)) // material ID { sscanf(str, "ID %d", &material_id); } else if (!strncmp(str, "MAT", 3)) // material ID { sscanf(str, "MAT %d", &material_id); } else if (!strncmp(str, "TCOL", 4)) // material ID { sscanf(str, "TCOL %d", &material_id); } else if (!strncmp(str, "TNAM", 4)) // TIN name { tin_name = str + 5; } else if (!strncmp(str, "VERT", 4)) // Beginning of vertices { sscanf(buf, "VERT %d\n", &num_points); DPoint2 p; float z; int optional; for (int i = 0; i < num_points; i++) { if (fgets(buf, 256, fp) == NULL) break; // First three are X, Y, Z. Optional fourth is "ID" or "locked". sscanf(buf, "%lf %lf %f %d", &p.x, &p.y, &z, &optional); #if 0 // Some files have Y/-Z flipped (but they are non-standard) double temp = p.y; p.y = -z; z = temp; #endif AddVert(p, z); if ((i%200) == 0 && progress_callback != NULL) { if (progress_callback(i * 49 / num_points)) { fclose(fp); return false; // user cancelled } } } } else if (!strncmp(str, "TRI", 3)) // Beginning of triangles { int num_faces; sscanf(str, "TRI %d\n", &num_faces); int v[3]; for (int i = 0; i < num_faces; i++) { fscanf(fp, "%d %d %d\n", v, v+2, v+1); // the indices in the file are 1-based, so subtract 1 AddTri(v[0]-1, v[1]-1, v[2]-1); if ((i%200) == 0 && progress_callback != NULL) { if (progress_callback(49 + i * 50 / num_faces)) { fclose(fp); return false; // user cancelled } } } } } fclose(fp); ComputeExtents(); return true; }
bool vtElevLayer::ImportFromFile(const wxString &strFileName, bool progress_callback(int), vtElevError *err) { // Avoid trouble with '.' and ',' in Europe - all the file readers assume // the default "C" locale. ScopedLocale normal_numbers(LC_NUMERIC, "C"); wxString strExt = strFileName.AfterLast('.'); vtString fname = (const char *) strFileName.mb_str(wxConvUTF8); VTLOG("ImportFromFile '%s'\n", (const char *) fname); if (!strExt.CmpNoCase(_T("gz"))) { // ignore .gz, look at extension under it wxString dropped = strFileName.Left(strFileName.Len()-3); strExt = dropped.AfterLast('.'); } if (!strExt.CmpNoCase(_T("bz2"))) { // ignore .bz2, look at extension under it wxString dropped = strFileName.Left(strFileName.Len()-4); strExt = dropped.AfterLast('.'); } // The first character in the file is useful for telling which format // the file really is. FILE *fp = vtFileOpen(fname, "rb"); char first = fgetc(fp); fclose(fp); bool success = false; if (!strExt.CmpNoCase(_T("dxf"))) { m_pTin = new vtTin2d; success = m_pTin->ReadDXF(fname, progress_callback); } else if (!strFileName.Right(6).CmpNoCase(_T("xy.adf"))) { m_pTin = new vtTin2d; success = m_pTin->ReadADF(fname, progress_callback); } else if (!strFileName.Right(4).CmpNoCase(_T(".tin"))) { m_pTin = new vtTin2d; success = m_pTin->ReadGMS(fname, progress_callback); } else if (!strFileName.Right(4).CmpNoCase(_T(".ply"))) { m_pTin = new vtTin2d; success = m_pTin->ReadPLY(fname, progress_callback); } else { if (m_pGrid == NULL) m_pGrid = new vtElevationGrid; } if (!strExt.CmpNoCase(_T("3tx"))) { success = m_pGrid->LoadFrom3TX(fname, progress_callback); } else if (!strExt.CmpNoCase(_T("dem"))) { // If there is a .hdr file in the same place, it is most likely // a GTOPO30/SRTM30 file vtString hdr_fname = ChangeFileExtension(fname, ".hdr"); if (vtFileExists(hdr_fname)) success = m_pGrid->LoadFromGTOPO30(hdr_fname, progress_callback); else { if (first == '*') success = m_pGrid->LoadFromMicroDEM(fname, progress_callback); else success = m_pGrid->LoadFromDEM(fname, progress_callback, err); } } else if (!strExt.CmpNoCase(_T("asc"))) { success = m_pGrid->LoadFromASC(fname, progress_callback); // vtElevationGrid does have its own ASC reader, but use GDAL instead // success = m_pGrid->LoadWithGDAL(strFileName.mb_str(wxConvUTF8), progress_callback, err); } else if (!strExt.CmpNoCase(_T("bil"))) { success = m_pGrid->LoadWithGDAL(fname, progress_callback, err); } else if (!strExt.CmpNoCase(_T("mem"))) { success = m_pGrid->LoadWithGDAL(fname, progress_callback, err); } else if (!strExt.CmpNoCase(_T("ter"))) { success = m_pGrid->LoadFromTerragen(fname, progress_callback); } else if (!strExt.CmpNoCase(_T("cdf"))) { success = m_pGrid->LoadWithGDAL(fname, progress_callback, err); } else if (!strExt.CmpNoCase(_T("hdr"))) { success = m_pGrid->LoadFromGTOPO30(fname, progress_callback); if (!success) success = m_pGrid->LoadFromGLOBE(fname, progress_callback); } else if (!strExt.CmpNoCase(_T("dte")) || !strExt.CmpNoCase(_T("dt0")) || !strExt.CmpNoCase(_T("dt1")) || !strExt.CmpNoCase(_T("dt2"))) { success = m_pGrid->LoadFromDTED(fname, progress_callback); } else if (!strExt.Left(3).CmpNoCase(_T("pgm"))) { success = m_pGrid->LoadFromPGM(fname, progress_callback); } else if (!strExt.CmpNoCase(_T("grd"))) { // might by CDF, might be Surfer GRD if (first == 'D') { VTLOG("First character is 'D', attempting load as a Surfer Grid file.\n"); success = m_pGrid->LoadFromGRD(fname, progress_callback); } else { VTLOG("First character is not 'D', attempting load as a netCDF file.\n"); success = m_pGrid->LoadWithGDAL(fname, progress_callback, err); } if (!success) { VTLOG("Didn't load successfully, attempting load with GDAL.\n"); // Might be 'Arc Binary Grid', try GDAL success = m_pGrid->LoadWithGDAL(fname, progress_callback, err); } } else if (!strFileName.Right(8).CmpNoCase(_T("catd.ddf")) || !strExt.Left(3).CmpNoCase(_T("tif")) || !strExt.Left(3).CmpNoCase(_T("png")) || !strExt.Left(3).CmpNoCase(_T("img")) || !strExt.CmpNoCase(_T("adf"))) { if (m_pGrid) success = m_pGrid->LoadWithGDAL(fname, progress_callback, err); } else if (!strExt.CmpNoCase(_T("raw"))) { RawDlg dlg(NULL, -1, _("Raw Elevation File")); dlg.m_iBytes = 2; dlg.m_iWidth = 100; dlg.m_iHeight = 100; dlg.m_fVUnits = 1.0f; dlg.m_fSpacing = 30.0f; dlg.m_bBigEndian = false; dlg.m_extents.SetToZero(); g_bld->GetProjection(dlg.m_original); if (dlg.ShowModal() == wxID_OK) { success = m_pGrid->LoadFromRAW(fname, dlg.m_iWidth, dlg.m_iHeight, dlg.m_iBytes, dlg.m_fVUnits, dlg.m_bBigEndian, progress_callback); } if (success) { m_pGrid->SetEarthExtents(dlg.m_extents); m_pGrid->SetProjection(dlg.m_proj); } } else if (!strExt.CmpNoCase(_T("ntf"))) { success = m_pGrid->LoadFromNTF5(fname, progress_callback); } else if (!strExt.CmpNoCase(_T("txt")) || !strExt.CmpNoCase(_T("xyz"))) { success = m_pGrid->LoadFromXYZ(fname, progress_callback); } else if (!strExt.CmpNoCase(_T("hgt"))) { success = m_pGrid->LoadFromHGT(fname, progress_callback); } else if (!strExt.Left(2).CmpNoCase(_T("db"))) { success = ImportFromDB(fname, progress_callback); } if (!success) return false; vtProjection *pProj; if (m_pGrid) pProj = &m_pGrid->GetProjection(); else pProj = &m_pTin->m_proj; // We should ask for a CRS before asking for extents if (!g_bld->ConfirmValidCRS(pProj)) { if (err) { err->type = vtElevError::CANCELLED; err->message = "Cancelled"; } return false; } if (m_pGrid != NULL) { if (m_pGrid->GetEarthExtents().IsEmpty()) { // No extents. wxString msg = _("File lacks geographic location (extents). Would you like to specify extents?\n Yes - specify extents\n No - use some default values\n"); int res = wxMessageBox(msg, _("Elevation Import"), wxYES_NO | wxCANCEL); if (res == wxYES) { DRECT ext; ext.SetToZero(); ExtentDlg dlg(NULL, -1, _("Elevation Grid Extents")); dlg.SetArea(ext, (pProj->IsGeographic() != 0)); if (dlg.ShowModal() == wxID_OK) m_pGrid->SetEarthExtents(dlg.m_area); else return false; } if (res == wxNO) { // Just make up some fake extents, assuming a regular even grid int xsize, ysize; m_pGrid->GetDimensions(xsize, ysize); DRECT ext; ext.left = ext.bottom = 0; if (pProj->IsGeographic()) { ext.right = xsize * (1.0/3600); // arc second ext.top = ysize * (1.0/3600); } else { ext.right = xsize * 10; // 10 linear units (meters, feet..) ext.top = ysize * 10; } m_pGrid->SetEarthExtents(ext); } if (res == wxCANCEL) { if (err) { err->type = vtElevError::CANCELLED; err->message = "Cancelled"; } return false; } } m_pGrid->SetupLocalCS(1.0f); } return true; }
void vtLog::StartLog(const char *fname) { m_log = vtFileOpen(fname, "wb"); }
bool vtTin::ReadPLY(const char *fname, bool progress_callback(int)) { FILE *fp = vtFileOpen(fname, "rb"); if (!fp) return false; VTLOG("ReadPLY '%s'\n", fname); char buf[256]; int material_id; int num_points; int num_faces; // first line is file identifier if (fgets(buf, 256, fp) == NULL) return false; if (strncmp(buf, "ply", 3) != 0) return false; while (fgets(buf, 256, fp) != NULL) { // trim trailing EOL characters vtString vstr = buf; vstr.Remove('\r'); vstr.Remove('\n'); const char *str = (const char *)vstr; if (!strncmp(str, "format", 6)) // beginning of TIN block continue; if (!strncmp(str, "ID", 2)) // material ID { sscanf(str, "ID %d", &material_id); } else if (!strncmp(str, "MAT", 3)) // material ID { sscanf(str, "MAT %d", &material_id); } else if (!strncmp(str, "TCOL", 4)) // material ID { sscanf(str, "TCOL %d", &material_id); } else if (!strncmp(str, "element vertex", 14)) // Number of vertices { sscanf(buf, "element vertex %d\n", &num_points); } else if (!strncmp(str, "element face", 12)) // Number of triangles { sscanf(buf, "element face %d\n", &num_faces); } else if (!strncmp(str, "end_header", 10)) { DPoint2 p; float z; int optional; VTLOG("ReadPLY num_points %d\n", num_points); for (int i = 0; i < num_points; i++) { if (fgets(buf, 256, fp) == NULL) break; // First three are X, Y, Z. Optional fourth is "ID" or "locked". sscanf(buf, "%lf %lf %f %d", &p.x, &p.y, &z, &optional); #if 0 // Some files have Y/-Z flipped (but they are non-standard) double temp = p.y; p.y = -z; z = temp; #endif AddVert(p, z); if ((i%200) == 0 && progress_callback != NULL) { if (progress_callback(i * 49 / num_points)) { fclose(fp); return false; // user cancelled } } } // Then read the triangles VTLOG("ReadPLY num_faces %d\n", num_faces); int inu, a, b, c; for (int i = 0; i < num_faces; i++) { if (fgets(buf, 256, fp) == NULL) break; sscanf(buf, "%d %d %d %d\n", &inu, &a, &b, &c); AddTri(a, b, c); if ((i%200) == 0 && progress_callback != NULL) { if (progress_callback(49 + i * 50 / num_faces)) { fclose(fp); return false; // user cancelled } } } } } fclose(fp); ComputeExtents(); return true; }