Statistics
| Revision:

root / org.gvsig.dgn / trunk / org.gvsig.dgn / org.gvsig.dgn.provider / src / main / java / org / gvsig / fmap / dal / store / dgn / DGNStoreProvider.java @ 130

History | View | Annotate | Download (68.7 KB)

1
package org.gvsig.fmap.dal.store.dgn;
2

    
3
import java.awt.geom.AffineTransform;
4
import java.awt.geom.Arc2D;
5
import java.awt.geom.PathIterator;
6
import java.io.BufferedWriter;
7
import java.io.File;
8
import java.io.FileWriter;
9
import java.io.IOException;
10
import java.util.ArrayList;
11
import java.util.Date;
12
import java.util.HashMap;
13
import java.util.Iterator;
14
import java.util.List;
15
import java.util.Map;
16

    
17
import org.apache.commons.io.IOUtils;
18
import org.cresques.cts.IProjection;
19
import org.slf4j.Logger;
20
import org.slf4j.LoggerFactory;
21

    
22
import org.gvsig.fmap.dal.DALLocator;
23
import org.gvsig.fmap.dal.DataManager;
24
import org.gvsig.fmap.dal.DataServerExplorer;
25
import org.gvsig.fmap.dal.DataStoreNotification;
26
import org.gvsig.fmap.dal.DataTypes;
27
import org.gvsig.fmap.dal.FileHelper;
28
import org.gvsig.fmap.dal.exception.DataException;
29
import org.gvsig.fmap.dal.exception.InitializeException;
30
import org.gvsig.fmap.dal.exception.LoadException;
31
import org.gvsig.fmap.dal.exception.OpenException;
32
import org.gvsig.fmap.dal.exception.ReadException;
33
import org.gvsig.fmap.dal.exception.ValidateDataParametersException;
34
import org.gvsig.fmap.dal.feature.EditableFeatureAttributeDescriptor;
35
import org.gvsig.fmap.dal.feature.EditableFeatureType;
36
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
37
import org.gvsig.fmap.dal.feature.FeatureType;
38
import org.gvsig.fmap.dal.feature.exception.PerformEditingException;
39
import org.gvsig.fmap.dal.feature.spi.FeatureProvider;
40
import org.gvsig.fmap.dal.feature.spi.FeatureStoreProviderServices;
41
import org.gvsig.fmap.dal.feature.spi.memory.AbstractMemoryStoreProvider;
42
import org.gvsig.fmap.dal.resource.ResourceAction;
43
import org.gvsig.fmap.dal.resource.exception.AccessResourceException;
44
import org.gvsig.fmap.dal.resource.file.FileResource;
45
import org.gvsig.fmap.dal.resource.spi.ResourceConsumer;
46
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
47
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemServerExplorer;
48
import org.gvsig.fmap.dal.serverexplorer.filesystem.FilesystemServerExplorerParameters;
49
import org.gvsig.fmap.dal.spi.DataStoreProviderServices;
50
import org.gvsig.fmap.dal.store.dgn.lib.DGNElemArc;
51
import org.gvsig.fmap.dal.store.dgn.lib.DGNElemComplexHeader;
52
import org.gvsig.fmap.dal.store.dgn.lib.DGNElemCore;
53
import org.gvsig.fmap.dal.store.dgn.lib.DGNElemMultiPoint;
54
import org.gvsig.fmap.dal.store.dgn.lib.DGNElemText;
55
import org.gvsig.fmap.dal.store.dgn.lib.DGNFileHeader;
56
import org.gvsig.fmap.dal.store.dgn.lib.DGNLink;
57
import org.gvsig.fmap.dal.store.dgn.lib.DGNPoint;
58
import org.gvsig.fmap.dal.store.dgn.lib.DGNReader;
59
import org.gvsig.fmap.geom.Geometry;
60
import org.gvsig.fmap.geom.Geometry.SUBTYPES;
61
import org.gvsig.fmap.geom.Geometry.TYPES;
62
import org.gvsig.fmap.geom.GeometryLocator;
63
import org.gvsig.fmap.geom.GeometryManager;
64
import org.gvsig.fmap.geom.exception.CreateEnvelopeException;
65
import org.gvsig.fmap.geom.exception.CreateGeometryException;
66
import org.gvsig.fmap.geom.primitive.Envelope;
67
import org.gvsig.fmap.geom.primitive.GeneralPathX;
68
import org.gvsig.fmap.geom.primitive.OrientablePrimitive;
69
import org.gvsig.fmap.geom.primitive.Point;
70
import org.gvsig.fmap.geom.type.GeometryType;
71
import org.gvsig.tools.dynobject.exception.DynMethodException;
72

    
73
public class DGNStoreProvider extends AbstractMemoryStoreProvider implements
74
    ResourceConsumer {
75

    
76
    private static final Logger logger = LoggerFactory.getLogger(DGNStoreProvider.class);
77

    
78
    public static final String NAME = "DGN";
79
    public static final String DESCRIPTION = "DGN file";
80

    
81
    public static final String METADATA_DEFINITION_NAME = NAME;
82
    public static final String METADATA_DEFINITION_DESCRIPTION = "DGN File Store";
83

    
84
    public static final int LOAD_MODE_PLAIN = 0;
85
    public static final int LOAD_MODE_GROUP1 = 1;
86

    
87
    public static final int CROP_OPERATION_NONE = 0;
88
    public static final int CROP_OPERATION_CONTAINS = 1;
89
    public static final int CROP_OPERATION_COVERS = 2;
90
    public static final int CROP_OPERATION_COVEREDBY = 3;
91
    public static final int CROP_OPERATION_CROSSES = 4;
92
    public static final int CROP_OPERATION_DISJOINT = 5;
93
    public static final int CROP_OPERATION_INTERSECT = 6;
94
    public static final int CROP_OPERATION_OVERLAPS = 7;
95
    public static final int CROP_OPERATION_TOUCHES = 8;
96
    public static final int CROP_OPERATION_WITHIN = 9;
97

    
98
    public static final int GROUP_GEOMETRIES_NONE = 0;
99
    public static final int GROUP_GEOMETRIES_CONVEXHULL = 1;
100
    public static final int GROUP_GEOMETRIES_UNION = 2;
101
    public static final int GROUP_GEOMETRIES_INTERSECTION = 3;
102
    public static final int GROUP_GEOMETRIES_TOPOINTS = 4;
103
    public static final int GROUP_GEOMETRIES_TOLINES = 5;
104
    public static final int GROUP_GEOMETRIES_TOPOLYGONS = 6;
105
    public static final int GROUP_GEOMETRIES_TOPOLYGONS_FIX = 7;
106

    
107
    public static final String NAME_FIELD_ID = "ID";
108
    public static final String NAME_FIELD_GEOMETRY = "Geometry";
109
    public static final String NAME_FIELD_TYPE = "Type";
110
    public static final String NAME_FIELD_STYPE = "SType";
111
    public static final String NAME_FIELD_ENTITY = "Entity";
112
    public static final String NAME_FIELD_LEVEL = "Layer";
113
    public static final String NAME_FIELD_COLOR = "Color";
114
    public static final String NAME_FIELD_FILLCOLOR = "FillColor";
115
    public static final String NAME_FIELD_ELEVATION = "Elevation";
116
    public static final String NAME_FIELD_WEIGHT = "Weight";
117
    public static final String NAME_FIELD_TEXT = "Text";
118
    public static final String NAME_FIELD_HEIGHTTEXT = "HeightText";
119
    public static final String NAME_FIELD_HEIGHTTEXTRAW = "HeightTextRaw";
120
    public static final String NAME_FIELD_ROTATIONTEXT = "Rotation";
121
    public static final String NAME_FIELD_STYLE = "Style";
122
    public static final String NAME_FIELD_GROUP = "Group";
123
    public static final String NAME_FIELD_ISSHAPE = "IsShape";
124
    public static final String NAME_FIELD_ISCOMPLEXSHAPEHEADER = "IsComplexShapeHeader";
125
    public static final String NAME_FIELD_ISHOLE = "IsHole";
126
    public static final String NAME_FIELD_ISCOMPLEX = "IsComplex";
127
    public static final String NAME_FIELD_PARENTID = "ParentId";
128
    public static final String NAME_FIELD_SCALE = "Scale";
129
    public static final String NAME_FIELD_LINKS_COUNT = "LinksCount";
130
    public static final String NAME_FIELD_LINK_INDEX = "LinkIndex";
131
    public static final String NAME_FIELD_LINK_TYPE = "LinkType";
132
    public static final String NAME_FIELD_LINK_ENTITY = "LinkEntity";
133
    public static final String NAME_FIELD_LINK_MS = "LinkMS";
134
    public static final String NAME_FIELD_LINK_LENGTH = "LinkLength";
135
    public static final String NAME_FIELD_LINK_DATA = "LinkData";
136
    public static final String NAME_FIELD_DATA = "Data";
137

    
138
    private int ID_FIELD_ID;
139
    private int ID_FIELD_TYPE;
140
    private int ID_FIELD_STYPE;
141
    private int ID_FIELD_ENTITY;
142
    private int ID_FIELD_LEVEL;
143
    private int ID_FIELD_COLOR;
144
    private int ID_FIELD_FILLCOLOR;
145
    private int ID_FIELD_ELEVATION;
146
    private int ID_FIELD_WEIGHT;
147
    private int ID_FIELD_TEXT;
148
    private int ID_FIELD_HEIGHTTEXT;
149
    private int ID_FIELD_HEIGHTTEXTRAW;
150
    private int ID_FIELD_ROTATIONTEXT;
151
    private int ID_FIELD_STYLE;
152
    private int ID_FIELD_GROUP;
153
    private int ID_FIELD_LAYER;
154
    private int ID_FIELD_ISCOMPLEXSHAPEHEADER;
155
    private int ID_FIELD_ISSHAPE;
156
    private int ID_FIELD_ISHOLE;
157
    private int ID_FIELD_ISCOMPLEX;
158
    private int ID_FIELD_PARENT;
159
    private int ID_FIELD_SCALE;
160
    private int ID_FIELD_LINKS_COUNT;
161
    private int ID_FIELD_LINK_INDEX;
162
    private int ID_FIELD_LINK_TYPE;
163
    private int ID_FIELD_LINK_ENTITY;
164
    private int ID_FIELD_LINK_MS;
165
    private int ID_FIELD_LINK_LENGTH;
166
    private int ID_FIELD_LINK_DATA;
167
    private int ID_FIELD_DATA;
168
    private int ID_FIELD_GEOMETRY;
169
    private int MAX_FIELD_ID;
170

    
171
    private IProjection projection;
172
    private ResourceProvider resource;
173
    private LegendBuilder legendBuilder;
174

    
175
    private long counterNewsOIDs = 0;
176
    protected GeometryManager geomManager = GeometryLocator
177
        .getGeometryManager();
178

    
179
    private int groupByFieldIndex = -2;
180
    private Map<Object, FeatureProvider> groupedFeatures = null;
181

    
182
    DGNData dgndata = null;
183

    
184
    public DGNStoreProvider(DGNStoreParameters parameters,
185
        DataStoreProviderServices storeServices) throws InitializeException {
186
        super(parameters, storeServices, FileHelper
187
            .newMetadataContainer(METADATA_DEFINITION_NAME));
188

    
189
        counterNewsOIDs = 0;
190
        // projection = CRSFactory.getCRS(getParameters().getSRSID());
191

    
192
        File file = getDGNParameters().getFile();
193
        resource =
194
            this.createResource(FileResource.NAME,
195
                new Object[] { file.getAbsolutePath() });
196

    
197
        resource.addConsumer(this);
198

    
199
        this.projection = this.getDGNParameters().getCRS();
200

    
201
        try {
202
            legendBuilder =
203
                (LegendBuilder) this.invokeDynMethod(
204
                    LegendBuilder.DYNMETHOD_BUILDER_NAME, null);
205
        } catch (DynMethodException e) {
206
            legendBuilder = null;
207
        } catch (Exception e) {
208
            throw new InitializeException(e);
209
        }
210

    
211
        this.initializeFeatureTypes();
212

    
213
    }
214

    
215
    private DGNStoreParameters getDGNParameters() {
216
        return (DGNStoreParameters) this.getParameters();
217
    }
218

    
219
    public String getProviderName() {
220
        return NAME;
221
    }
222

    
223
    public boolean allowWrite() {
224
        // not yet
225
        return false;
226
    }
227

    
228
    public Object getLegend() throws OpenException {
229
        this.open();
230
        if (legendBuilder == null) {
231
            return null;
232
        }
233
        return legendBuilder.getLegend();
234
    }
235

    
236
    public Object getLabeling() throws OpenException {
237
        this.open();
238
        if (legendBuilder == null) {
239
            return null;
240
        }
241
        return legendBuilder.getLabeling();
242
    }
243

    
244
    private class DGNData {
245

    
246
        public List<FeatureProvider> data = null;
247
        public FeatureType defaultFType = null;
248
        public List<FeatureType> fTypes = null;
249
        public Envelope envelope = null;
250
        public IProjection projection;
251
        public LegendBuilder legendBuilder;
252

    
253
        public Envelope getEnvelopeCopy() throws CreateEnvelopeException {
254
            if (envelope == null) {
255
                return null;
256
            }
257
            try {
258
                return (Envelope) envelope.clone();
259
            } catch (CloneNotSupportedException ex) {
260
                logger.warn("Can't clone envelope.", ex);
261
                return null;
262
            }
263
        }
264
    }
265

    
266
    public static class TimeCounter {
267

    
268
        private static final Logger logger = LoggerFactory
269
            .getLogger(TimeCounter.class);
270

    
271
        private long counter = 0;
272
        private Date t1;
273
        private Date t2;
274

    
275
        public void start() {
276
            this.t1 = new Date();
277
            this.t2 = this.t1;
278
        }
279

    
280
        public void restart() {
281
            this.t1 = new Date();
282
            this.t2 = this.t1;
283
            this.counter = 0;
284
        }
285

    
286
        public void stop() {
287
            this.t2 = new Date();
288
            this.counter += this.t2.getTime() - this.t1.getTime();
289
        }
290

    
291
        public long get() {
292
            return this.counter;
293
        }
294

    
295
        public void log(String msg) {
296
            logger.debug("Time " + get() + " ms. " + msg);
297
        }
298

    
299
        public void restart(String msg) {
300
            this.stop();
301
            this.log(msg);
302
            this.restart();
303
        }
304
    }
305

    
306
    public void open() throws OpenException {
307
        if (this.data != null) {
308
            return;
309
        }
310
        try {
311
            getResource().execute(new ResourceAction() {
312

    
313
                public Object run() throws Exception {
314

    
315
                    TimeCounter tc = new TimeCounter();
316
                    tc.start();
317

    
318
                    FeatureStoreProviderServices storeProviderServices = getStoreServices();
319
                    if (dgndata == null && !(getDGNParameters().useReload())) {
320
                        if (resource.getData() != null) {
321
                            dgndata =
322
                                (DGNData) ((Map) resource.getData())
323
                                    .get(projection.getAbrev()); // OJO no es del todo correcto (puede llevar reproyeccion)
324
                        } else {
325
                            resource.setData(new HashMap());
326
                        }
327
                    }
328
                    tc.restart("Retrive data from resource (data=" + dgndata + ")");
329

    
330
                    if (dgndata == null) {
331
                        dgndata = new DGNData();
332
                        dgndata.data = new ArrayList();
333
                        data = dgndata.data;
334
                        counterNewsOIDs = 0;
335
                        Reader reader = new Reader().initialice(getMemoryProvider(), (File) resource.get(), projection, legendBuilder);
336
                        reader.begin(storeProviderServices);
337
                        dgndata.defaultFType = reader.getDefaultType().getNotEditableCopy();
338
                        List types = new ArrayList();
339
                        Iterator it = reader.getTypes().iterator();
340
                        EditableFeatureType fType;
341
                        while (it.hasNext()) {
342
                            fType = (EditableFeatureType) it.next();
343
                            if (fType.getId().equals(dgndata.defaultFType.getId())) {
344
                                types.add(dgndata.defaultFType);
345
                            } else {
346
                                types.add(fType.getNotEditableCopy());
347
                            }
348
                        }
349
                        dgndata.fTypes = types;
350

    
351
                        resource.notifyOpen();
352
                        storeProviderServices.setFeatureTypes(dgndata.fTypes, dgndata.defaultFType);
353
                        reader.load();
354
                        dgndata.envelope = reader.getEnvelope();
355
                        dgndata.legendBuilder = legendBuilder;
356
                        dgndata.projection = projection;
357
                        reader.end();
358
                        if (resource.getData() == null) {
359
                            resource.setData(new HashMap());
360
                        }
361
                        ((Map) resource.getData()).put(projection.getAbrev(),dgndata); // OJO la reproyeccion
362
                        tc.restart("Loaded data from file (data=" + dgndata + ")");
363
                        resource.notifyClose();
364
                    }
365
                    // El feature type no lo compartimos entre las instancias del
366
                    // mismo resource ya que puede cambiar en funcion del filtro.
367
                    // Por lo menos el geometry-type.
368
                    List<FeatureType> featureTypes = getFeatureTypes(storeProviderServices);
369

    
370
                    tc.restart("Created featuretype (featureTypes=" + featureTypes + ")");
371

    
372
                    PostProcessFeatures postProcess = new PostProcessFeatures(getDGNParameters(), featureTypes.get(0));
373
                    if (postProcess.hasOperations()) {
374
                        data = postProcess.apply(dgndata.data);
375
                        setDynValue("Envelope", postProcess.getEnvelope());
376
                    } else {
377
                        data = dgndata.data;
378
                        setDynValue("Envelope", dgndata.getEnvelopeCopy());
379
                    }
380
                    tc.restart("PostProcessFeatures");
381

    
382
                    legendBuilder = dgndata.legendBuilder;
383
                    storeProviderServices.setFeatureTypes(featureTypes, featureTypes.get(0));
384
                    setDynValue("CRS", projection);
385
                    counterNewsOIDs = data.size();
386
                    tc.restart("load finished.");
387
                    return null;
388
                }
389
            });
390
        } catch (Exception e) {
391
            data = null;
392
            try {
393
                throw new OpenException(resource.getName(), e);
394
            } catch (AccessResourceException e1) {
395
                throw new OpenException(getProviderName(), e);
396
            }
397
        }
398
    }
399

    
400
    public DataServerExplorer getExplorer() throws ReadException {
401
        DataManager manager = DALLocator.getDataManager();
402
        FilesystemServerExplorerParameters params;
403
        try {
404
            params = (FilesystemServerExplorerParameters) manager.createServerExplorerParameters(FilesystemServerExplorer.NAME);
405
            params.setRoot(this.getDGNParameters().getFile().getParent());
406
            return manager.openServerExplorer(FilesystemServerExplorer.NAME, params);
407
        } catch (DataException e) {
408
            throw new ReadException(this.getProviderName(), e);
409
        } catch (ValidateDataParametersException e) {
410
            throw new ReadException(this.getProviderName(), e);
411
        }
412

    
413
    }
414

    
415
    public void performChanges(Iterator deleteds, Iterator inserteds,
416
        Iterator updateds, Iterator originalFeatureTypesUpdated)
417
        throws PerformEditingException {
418
        // FIXME Exception
419
        throw new UnsupportedOperationException();
420
    }
421

    
422
    public List getFeatureTypes(FeatureStoreProviderServices store) {
423
        //FIXME: Habr?a que distinguir cuando se va a crear un DGN 3D o 2D, de momento siempre 3D
424
        return getFeatureTypes(store, Geometry.SUBTYPES.GEOM3D);
425
    }
426

    
427
    private List getFeatureTypes(FeatureStoreProviderServices store, int subtype) {
428
        EditableFeatureType featureType = store.createFeatureType(getName());
429

    
430
        featureType.setHasOID(true);
431

    
432
        ID_FIELD_ID = featureType.add(NAME_FIELD_ID, DataTypes.INT)
433
                .setDefaultValue(Integer.valueOf(0)).getIndex();
434

    
435
        ID_FIELD_PARENT = featureType.add(NAME_FIELD_PARENTID, DataTypes.INT)
436
                .setDefaultValue(Integer.valueOf(0)).getIndex();
437

    
438
        // FIXME: Cual es el size y el valor por defecto para Entity ?
439
        ID_FIELD_ENTITY = featureType.add(NAME_FIELD_ENTITY, DataTypes.STRING, 100)
440
                .setDefaultValue("").getIndex();
441

    
442
        // FIXME: Cual es el size de Layer ?
443
        ID_FIELD_LEVEL = featureType.add(NAME_FIELD_LEVEL, DataTypes.STRING, 100)
444
                .setDefaultValue("default").getIndex();
445
        ID_FIELD_LAYER = ID_FIELD_LEVEL;
446

    
447
        ID_FIELD_COLOR = featureType.add(NAME_FIELD_COLOR, DataTypes.INT)
448
                .setDefaultValue(Integer.valueOf(0)).getIndex();
449

    
450
        // FIXME: Cual es el size de Text ?
451
        ID_FIELD_TEXT = featureType.add(NAME_FIELD_TEXT, DataTypes.STRING, 100)
452
                .setDefaultValue("").getIndex();
453

    
454
        ID_FIELD_HEIGHTTEXT = featureType.add(NAME_FIELD_HEIGHTTEXT, DataTypes.DOUBLE)
455
                .setDefaultValue(Double.valueOf(10)).getIndex();
456

    
457
        ID_FIELD_HEIGHTTEXTRAW = featureType.add(NAME_FIELD_HEIGHTTEXTRAW, DataTypes.DOUBLE)
458
                .setDefaultValue(Double.valueOf(10)).getIndex();
459

    
460
        ID_FIELD_ROTATIONTEXT = featureType.add(NAME_FIELD_ROTATIONTEXT, DataTypes.DOUBLE)
461
                .setDefaultValue(Double.valueOf(0)).getIndex();
462

    
463
        ID_FIELD_TYPE = featureType.add(NAME_FIELD_TYPE, DataTypes.INT)
464
                .setDefaultValue(Integer.valueOf(0)).getIndex();
465

    
466
        ID_FIELD_STYPE = featureType.add(NAME_FIELD_STYPE, DataTypes.INT)
467
                .setDefaultValue(Integer.valueOf(0)).getIndex();
468

    
469
        ID_FIELD_FILLCOLOR = featureType.add(NAME_FIELD_FILLCOLOR, DataTypes.INT)
470
                .setDefaultValue(Integer.valueOf(0)).getIndex();
471

    
472
        ID_FIELD_STYLE = featureType.add(NAME_FIELD_STYLE, DataTypes.INT)
473
                .setDefaultValue(Integer.valueOf(0)).getIndex();
474

    
475
        ID_FIELD_ELEVATION = featureType.add(NAME_FIELD_ELEVATION, DataTypes.DOUBLE)
476
                .setDefaultValue(Double.valueOf(0)).getIndex();
477

    
478
        ID_FIELD_WEIGHT = featureType.add(NAME_FIELD_WEIGHT, DataTypes.DOUBLE)
479
                .setDefaultValue(Double.valueOf(0)).getIndex();
480

    
481
        ID_FIELD_GROUP = featureType.add(NAME_FIELD_GROUP, DataTypes.INT)
482
                .setDefaultValue(Integer.valueOf(0)).getIndex();
483

    
484
        ID_FIELD_ISSHAPE = featureType.add(NAME_FIELD_ISSHAPE, DataTypes.BOOLEAN)
485
                .setDefaultValue(Boolean.FALSE).getIndex();
486

    
487
        ID_FIELD_ISCOMPLEXSHAPEHEADER = featureType.add(NAME_FIELD_ISCOMPLEXSHAPEHEADER, DataTypes.BOOLEAN)
488
                .setDefaultValue(Boolean.FALSE).getIndex();
489

    
490
        ID_FIELD_ISHOLE = featureType.add(NAME_FIELD_ISHOLE, DataTypes.BOOLEAN)
491
                .setDefaultValue(Boolean.FALSE).getIndex();
492

    
493
        ID_FIELD_ISCOMPLEX = featureType.add(NAME_FIELD_ISCOMPLEX, DataTypes.BOOLEAN)
494
                .setDefaultValue(Boolean.FALSE).getIndex();
495

    
496
        ID_FIELD_SCALE = featureType.add(NAME_FIELD_SCALE, DataTypes.INT)
497
                .setDefaultValue(Integer.valueOf(0)).getIndex();
498

    
499
        ID_FIELD_LINKS_COUNT = featureType.add(NAME_FIELD_LINKS_COUNT, DataTypes.INT)
500
                .setDefaultValue(Integer.valueOf(0)).getIndex();
501

    
502
        ID_FIELD_LINK_INDEX = featureType.add(NAME_FIELD_LINK_INDEX, DataTypes.INT)
503
                .setDefaultValue(Integer.valueOf(0)).getIndex();
504

    
505
        ID_FIELD_LINK_ENTITY = featureType.add(NAME_FIELD_LINK_ENTITY, DataTypes.INT)
506
                .setDefaultValue(Integer.valueOf(0)).getIndex();
507

    
508
        ID_FIELD_LINK_TYPE = featureType.add(NAME_FIELD_LINK_TYPE, DataTypes.INT)
509
                .setDefaultValue(Integer.valueOf(0)).getIndex();
510

    
511
        ID_FIELD_LINK_MS = featureType.add(NAME_FIELD_LINK_MS, DataTypes.INT)
512
                .setDefaultValue(Integer.valueOf(0)).getIndex();
513

    
514
        ID_FIELD_LINK_LENGTH = featureType.add(NAME_FIELD_LINK_LENGTH, DataTypes.INT)
515
                .setDefaultValue(Integer.valueOf(0)).getIndex();
516

    
517
        ID_FIELD_LINK_DATA = featureType.add(NAME_FIELD_LINK_DATA, DataTypes.STRING, 512)
518
                .setDefaultValue("").getIndex();
519

    
520
        ID_FIELD_DATA = featureType.add(NAME_FIELD_DATA, DataTypes.STRING, 512)
521
                .setDefaultValue("").getIndex();
522

    
523
        EditableFeatureAttributeDescriptor attr = featureType.add(NAME_FIELD_GEOMETRY, DataTypes.GEOMETRY);
524
        attr.setSRS(this.projection);
525
        int geometryTypeToUse = getDGNParameters().getGeometryTypeFilter();
526
        if (getDGNParameters().getGroupBy() != null) {
527
            switch (getDGNParameters().getGroupGeometriesOperation()) {
528
            case GROUP_GEOMETRIES_NONE:
529
            case GROUP_GEOMETRIES_UNION:
530
            case GROUP_GEOMETRIES_INTERSECTION:
531
                break;
532
            case GROUP_GEOMETRIES_TOPOINTS:
533
                geometryTypeToUse = Geometry.TYPES.MULTIPOINT;
534
                break;
535
            case GROUP_GEOMETRIES_TOLINES:
536
                geometryTypeToUse = Geometry.TYPES.MULTICURVE;
537
                break;
538
            case GROUP_GEOMETRIES_TOPOLYGONS:
539
            case GROUP_GEOMETRIES_TOPOLYGONS_FIX:
540
            case GROUP_GEOMETRIES_CONVEXHULL:
541
                geometryTypeToUse = Geometry.TYPES.MULTISURFACE;
542
                break;
543
            }
544
        }
545
        try {
546
            attr.setGeometryType(GeometryLocator.getGeometryManager()
547
                .getGeometryType(geometryTypeToUse, subtype));
548
        } catch (Exception e) {
549
            attr.setGeometryType(geometryTypeToUse);
550
            attr.setGeometrySubType(subtype);
551
        }
552
        ID_FIELD_GEOMETRY = attr.getIndex();
553

    
554
        featureType.setDefaultGeometryAttributeName(NAME_FIELD_GEOMETRY);
555

    
556
        MAX_FIELD_ID = featureType.size() - 1;
557

    
558
        List types = new ArrayList();
559
        types.add(featureType);
560

    
561
        return types;
562
    }
563

    
564
    public class Reader {
565

    
566
        private File file;
567
        private IProjection projection;
568
        private List types;
569
        private LegendBuilder leyendBuilder;
570
        private AbstractMemoryStoreProvider storeProvider;
571
        private Envelope envelope;
572

    
573
        public Reader initialice(AbstractMemoryStoreProvider storeProvider, File file,
574
            IProjection projection, LegendBuilder leyendBuilder) {
575
            this.storeProvider = storeProvider;
576
            this.file = file;
577
            this.projection = projection;
578
            this.leyendBuilder = leyendBuilder;
579
            if (leyendBuilder != null) {
580
                leyendBuilder.initialize(storeProvider);
581
            }
582
            return this;
583
        }
584

    
585
        public Envelope getEnvelope() {
586
            return this.envelope;
587
        }
588

    
589
        public void begin(FeatureStoreProviderServices storeProviderServices) {
590

    
591
            types = getFeatureTypes(storeProviderServices);
592

    
593
            if (leyendBuilder != null) {
594
                leyendBuilder.begin();
595
            }
596

    
597
        }
598

    
599
        public void end() {
600
            if (leyendBuilder != null) {
601
                leyendBuilder.end();
602
            }
603
        }
604

    
605
        public List getTypes() {
606
            return types;
607
        }
608

    
609
        public EditableFeatureType getDefaultType() {
610
            return (EditableFeatureType) types.get(0);
611
        }
612

    
613
        public void load() throws DataException, CreateEnvelopeException {
614
            switch (getDGNParameters().getLoadMode()) {
615
            case LOAD_MODE_PLAIN:
616
            default:
617
                load_plain();
618
                break;
619
            case LOAD_MODE_GROUP1:
620
                load_group1();
621
                break;
622
            }
623
        }
624

    
625
        public void load_plain() throws DataException {
626

    
627
            FileWriter xmlfw = null;
628
            BufferedWriter xmlbfw = null;
629

    
630
            this.envelope = null;
631

    
632
            boolean ignoreZs = getDGNParameters().ignoreZs();
633
            boolean useZAsElevation = getDGNParameters().useZAsElevation();
634
            boolean applyRoundToElevation = getDGNParameters().getApplyRoundToElevation();
635
            double elevationFactor = getDGNParameters().geElevationFactor();
636

    
637
            try {
638
                if (getDGNParameters().getXMLFile() != null) {
639
                    File xmlfile = getDGNParameters().getXMLFile();
640
                    try {
641
                        xmlfw = new FileWriter(xmlfile);
642
                        xmlbfw = new BufferedWriter(xmlfw);
643

    
644
                    } catch (Exception ex) {
645
                        xmlfw = null;
646
                        xmlbfw = null;
647
                        logger.warn("Can't open xmfile for output (" + xmlfile.getAbsolutePath() + "'.", ex);
648
                    }
649
                    if (xmlbfw != null) {
650
                        try {
651
                            xmlbfw.write("<DGN>\n");
652
                        } catch (IOException ex) {
653
                            logger.warn("Can't write to the xml file.", ex);
654
                        }
655
                    }
656

    
657
                }
658

    
659
                DGNReader dgnReader = new DGNReader(file.getAbsolutePath(), getDGNParameters().logErrors());
660

    
661
                // FIXME: De momento forzamos a que todas las geometr?as que se
662
                // creen sean 3D porque el m?todo getFeatureTypes solo puede
663
                // crear los geometryAttributes como 3D
664
                int subtypeHeader;
665
                    subtypeHeader = Geometry.SUBTYPES.GEOM3D;
666

    
667
                envelope = geomManager.createEnvelope(subtypeHeader);
668

    
669
                FeatureType type = getDefaultType().getNotEditableCopy();
670

    
671
                int counterOfElement = 0;
672
                DGNElemComplexHeader parentElement = null;
673

    
674
                double zvalue = 0;
675

    
676
                for (int id = 0; id < dgnReader.getNumEntities(); id++) {
677
                    dgnReader.DGNGotoElement(id);
678

    
679
                    DGNElemCore elemento = dgnReader.DGNReadElement();
680
                    if (elemento == null) {
681
                        continue;
682
                    }
683

    
684
                    if (parentElement != null) {
685
                        counterOfElement++;
686
                        if (parentElement.getNumElements() < counterOfElement) {
687
                            // Ya hemos terminado de recorrer el elemento complejo.
688
                            parentElement = null;
689
                        }
690
                    }
691

    
692
                    if (xmlbfw != null) {
693
                        // Volcamos el elemnto del DGN a fichero en formato XML
694
                        try {
695
                            xmlbfw.write(dgnReader.DGNDumpElement(dgnReader.getInfo(), elemento));
696
                        } catch (IOException ex) {
697
                            logger.warn("Can't write to the xml file.", ex);
698
                        }
699
                    }
700

    
701
                    if (elemento.isDeleted()) {
702
                        // Saltamos los elementos borrados.
703
                        continue;
704
                    }
705

    
706
                    FeatureProvider data = createFeatureProvider(type);
707

    
708
                    data.set(ID_FIELD_ID, elemento.getID());
709
                    data.set(ID_FIELD_TYPE, elemento.getType());
710
                    data.set(ID_FIELD_STYPE, elemento.getSType());
711
                    data.set(ID_FIELD_LEVEL, elemento.getLevelAsString());
712
                    data.set(ID_FIELD_COLOR, elemento.getColor());
713
                    data.set(ID_FIELD_FILLCOLOR, elemento.getShapeFillColor());
714
                    data.set(ID_FIELD_ENTITY, elemento.getEntityName());
715
                    data.set(ID_FIELD_STYLE, elemento.getStyle());
716
                    data.set(ID_FIELD_WEIGHT, elemento.getWeight());
717
                    data.set(ID_FIELD_GROUP, elemento.getGroup());
718
                    data.set(ID_FIELD_ELEVATION, elemento.getElevation());
719
                    data.set(ID_FIELD_ISCOMPLEXSHAPEHEADER,elemento.isComplexShapeHeader());
720
                    data.set(ID_FIELD_ISSHAPE, elemento.isShape());
721
                    data.set(ID_FIELD_ISHOLE, elemento.isHole());
722
                    data.set(ID_FIELD_ISCOMPLEX, elemento.isComplex());
723
                    data.set(ID_FIELD_HEIGHTTEXT, 0);
724
                    data.set(ID_FIELD_HEIGHTTEXTRAW, 0);
725
                    data.set(ID_FIELD_ROTATIONTEXT, 0);
726
                    data.set(ID_FIELD_TEXT, null);
727
                    data.set(ID_FIELD_SCALE, dgnReader.getInfo().scale);
728
                    data.set(ID_FIELD_DATA, elemento.getDataAsHexadecimal());
729

    
730
                    if (parentElement == null) {
731
                        data.set(ID_FIELD_PARENT, elemento.getID());
732
                    } else {
733
                        data.set(ID_FIELD_PARENT, parentElement.getID());
734
                    }
735
                    data.set(ID_FIELD_LINKS_COUNT,dgnReader.DGNGetLinkageCount(elemento));
736

    
737
                    DGNLink dgnlink = dgnReader.DGNGetLinkage(elemento,
738
                            getDGNParameters().getLinkFilterIndex(),
739
                            getDGNParameters().getLinkFilterType(),
740
                            getDGNParameters().getLinkFilterEntity(),
741
                            getDGNParameters().getLinkFilterMS(),
742
                            getDGNParameters().getLinkFilterDataAsPattern()
743
                    );
744
                    if (dgnlink != null) {
745
                        data.set(ID_FIELD_LINK_INDEX, dgnlink.getIndex());
746
                        data.set(ID_FIELD_LINK_TYPE, dgnlink.getType());
747
                        data.set(ID_FIELD_LINK_ENTITY, dgnlink.getEntityCode());
748
                        data.set(ID_FIELD_LINK_MS, dgnlink.getMSLink());
749
                        data.set(ID_FIELD_LINK_LENGTH, dgnlink.getLength());
750
                        data.set(ID_FIELD_LINK_DATA,dgnlink.getDataAsHexadecimal());
751
                    } else {
752
                        data.set(ID_FIELD_LINK_INDEX, -1);
753
                        data.set(ID_FIELD_LINK_TYPE, 0);
754
                        data.set(ID_FIELD_LINK_ENTITY, 0);
755
                        data.set(ID_FIELD_LINK_MS, 0);
756
                        data.set(ID_FIELD_LINK_LENGTH, 0);
757
                        data.set(ID_FIELD_LINK_DATA, "");
758
                    }
759

    
760
                    zvalue = 0;
761
                    try {
762
                        int subtypeElement;
763
                        switch (elemento.stype) {
764
                        case DGNFileHeader.DGNST_COMPLEX_HEADER:
765
                            parentElement = (DGNElemComplexHeader) elemento;
766
                            counterOfElement = 0;
767
                            break;
768

    
769
                        case DGNFileHeader.DGNST_MULTIPOINT:
770
                            DGNElemMultiPoint dgnmultipoint = (DGNElemMultiPoint) elemento;
771
                            if (dgnmultipoint.isPoint()) {
772
                                DGNPoint p = dgnmultipoint.getPoint(0);
773
                                Point point = createPoint(p.getX(), p.getY(), ignoreZs ? 0 : p.getZ());
774
                                data.setDefaultGeometry(point);
775
                                zvalue = p.getZ();
776
                            } else {
777
                                OrientablePrimitive geom = null;
778
                                subtypeElement = getGeometrySubType(dgnmultipoint);
779
                                if (dgnmultipoint.isPolygon()) {
780
                                    geom = (OrientablePrimitive) geomManager.create(Geometry.TYPES.SURFACE,subtypeHeader);
781
                                } else {
782
                                    geom = (OrientablePrimitive) geomManager.create(Geometry.TYPES.CURVE,subtypeHeader);
783
                                }
784

    
785
                                // Si es una curva nos saltamos los dos primeros y los dos ultimos vertices.
786
                                int first = 0;
787
                                int numVertices = dgnmultipoint.getNumVertices();
788
                                if (dgnmultipoint.isCurve()) {
789
                                    first = 2;
790
                                    numVertices = dgnmultipoint.getNumVertices() - 2;
791
                                }
792

    
793
                                if (dgnmultipoint.isHole()) {
794
                                    // Invertimos el orden porque es un agujero
795
                                    for (int i = numVertices - 2; i >= first; i--) {
796
                                        DGNPoint p = dgnmultipoint.getVertex(i);
797
                                        zvalue = p.getZ();
798
                                        addVertex(geom, p.getX(), p.getY(), ignoreZs ? 0 : zvalue);
799
                                    }
800
                                } else {
801
                                    for (int i = first; i < numVertices; i++) {
802
                                        DGNPoint p = dgnmultipoint.getVertex(i);
803
                                        zvalue = p.getZ();
804
                                        addVertex(geom, p.getX(), p.getY(), ignoreZs ? 0 : zvalue);
805
                                    }
806
                                }
807
                                data.setDefaultGeometry(geom);
808
                            }
809
                            break;
810

    
811
                        case DGNFileHeader.DGNST_ARC:
812
                            DGNElemArc dgnarc = (DGNElemArc) elemento;
813

    
814
                            // La definici?n de arco de MicroStation es distinta a la de Java.
815
                            // En el dgn el origen se entiende que es el centro del arco,
816
                            // y a la hora de crear un Arc2D las 2 primeras coordenadas son
817
                            // la esquina inferior izquierda del rect?ngulo que rodea al arco.
818
                            // 1.- Creamos la elipse sin rotaci?n.
819
                            // 2.- Creamos el arco
820
                            // 3.- Rotamos el resultado
821
                            AffineTransform mT =
822
                                AffineTransform.getRotateInstance(
823
                                    Math.toRadians(dgnarc.rotation),
824
                                    dgnarc.origin.x, dgnarc.origin.y);
825

    
826
                            Arc2D.Double elArco = new Arc2D.Double(
827
                                    dgnarc.origin.x - dgnarc.primary_axis,
828
                                    dgnarc.origin.y - dgnarc.secondary_axis,
829
                                    2.0 * dgnarc.primary_axis,
830
                                2.0 * dgnarc.secondary_axis,
831
                                -dgnarc.startang, -dgnarc.sweepang,
832
                                Arc2D.OPEN);
833

    
834
                            zvalue = dgnarc.origin.getZ();
835

    
836
                            PathIterator pathIterator = elArco.getPathIterator(null);
837
                            GeneralPathX elShapeArc = new GeneralPathX(pathIterator);
838

    
839
                            // Transformamos el GeneralPahtX porque si transformamos
840
                            // elArco nos lo convierte a GeneralPath y nos guarda las coordenadas en float,
841
                            // con la correspondiente p?rdida de precisi?n
842
                            elShapeArc.transform(mT);
843

    
844
                            OrientablePrimitive geom = null;
845
                            subtypeElement = getGeometrySubType(dgnarc);
846
                            if (dgnarc.isSurface()) {
847
                                geom = (OrientablePrimitive) geomManager.create(
848
                                        Geometry.TYPES.SURFACE, subtypeHeader);
849
                            } else {
850
                                geom = (OrientablePrimitive) geomManager.create(
851
                                        Geometry.TYPES.CURVE, subtypeHeader);
852
                            }
853
                            geom.setGeneralPath(elShapeArc);
854
                            if ((subtypeElement == Geometry.SUBTYPES.GEOM3D || subtypeElement == Geometry.SUBTYPES.GEOM3DM)
855
                                && !ignoreZs) {
856
                                for (int i = 0; i < geom.getNumVertices(); i++) {
857
                                    Point originalvertex = geom.getVertex(i);
858
                                    Point vertex =
859
                                        (Point) geomManager.create(Geometry.TYPES.POINT, subtypeElement);
860
                                    vertex.setCoordinateAt(0,originalvertex.getX());
861
                                    vertex.setCoordinateAt(1,originalvertex.getY());
862
                                    vertex.setCoordinateAt(2, zvalue);
863
                                    geom.setVertex(i, vertex);
864
                                }
865
                            } else {
866
                                //do nothing
867
                                int a=0;
868
                            }
869

    
870
                            data.setDefaultGeometry(geom);
871
                            break;
872

    
873
                        case DGNFileHeader.DGNST_TEXT:
874
                            DGNElemText dgntext = (DGNElemText) elemento;
875
                            subtypeElement = getGeometrySubType(dgntext);
876
                            Point point = (Point) geomManager.create(TYPES.POINT, subtypeHeader);
877
                            point.setCoordinateAt(0, dgntext.getPoint().getX());
878
                            point.setCoordinateAt(1, dgntext.getPoint().getY());
879
                            if (subtypeHeader == Geometry.SUBTYPES.GEOM3D) {
880
                                if(subtypeElement == Geometry.SUBTYPES.GEOM2D){
881
                                    point.setCoordinateAt(2, 0);
882
                                } else {
883
                                    point.setCoordinateAt(2, dgntext.getPoint().getZ());
884
                                }
885
                            }
886

    
887
                            data.set(ID_FIELD_HEIGHTTEXT, dgntext.getHeight());
888
                            data.set(ID_FIELD_HEIGHTTEXTRAW,dgntext.getRawHeight());
889
                            data.set(ID_FIELD_ROTATIONTEXT,dgntext.getRotation());
890
                            data.set(ID_FIELD_TEXT, dgntext.getText());
891

    
892
                            data.setDefaultGeometry(point);
893
                            break;
894

    
895
                        default:
896
                            break;
897

    
898
                        } // switch
899
                    } catch (Exception ex) {
900
                        logger.warn("Can't process element", ex);
901
                    }
902
                    if (useZAsElevation) {
903
                        if (!DGNStoreProvider.equals(elevationFactor, 1, 0.00001)) {
904
                            zvalue = zvalue * elevationFactor;
905
                        }
906
                        if (applyRoundToElevation) {
907
                            zvalue = Math.round(zvalue);
908
                        }
909
                        data.set(ID_FIELD_ELEVATION, zvalue);
910
                    }
911
                    addFeature(data, dgnReader);
912
                } // for
913

    
914
                EditableFeatureType featureType = storeProvider.getFeatureStore().getDefaultFeatureType().getCopy().getEditable();
915
                FeatureAttributeDescriptor fad = (FeatureAttributeDescriptor) featureType.get(featureType.getDefaultGeometryAttributeName());
916
                featureType.remove(fad.getName());
917
                EditableFeatureAttributeDescriptor efad = featureType.add(fad.getName(), fad.getType(), fad.getSize());
918
                efad.setDefaultValue(fad.getDefaultValue());
919

    
920
                GeometryType gty = null;
921

    
922
                gty = geomManager.getGeometryType(fad.getGeomType().getType(),subtypeHeader);
923

    
924
                efad.setGeometryType(gty);
925

    
926
                efad.setPrecision(fad.getPrecision());
927
                featureType.setDefaultGeometryAttributeName(fad.getName());
928

    
929

    
930
                if (xmlbfw != null) {
931
                    try {
932
                        xmlbfw.write("</DGN>\n");
933
                    } catch (IOException ex) {
934
                        logger.warn("Can't write to the xml file.", ex);
935
                    }
936
                }
937

    
938
            } catch (Exception ex) {
939
                logger.warn("Can't process DGN file '" + getFullName() + ".",ex);
940
                throw new LoadException(ex, getFullName());
941
            } finally {
942
                IOUtils.closeQuietly(xmlbfw);
943
                IOUtils.closeQuietly(xmlfw);
944
            }
945

    
946
        }
947

    
948
        private int getGeometrySubType(DGNElemCore element) {
949
            if (getDGNParameters().force2D()) {
950
                return Geometry.SUBTYPES.GEOM2D;
951
            }
952
            return element.is3D() ? Geometry.SUBTYPES.GEOM3D : Geometry.SUBTYPES.GEOM2D;
953
        }
954

    
955
        private void addVertex(OrientablePrimitive geom, double x, double y,
956
            double z) {
957
            if (geom.getDimension() == 2) {
958
                geom.addVertex(x, y);
959
            } else {
960
                geom.addVertex(x, y, z);
961
            }
962
        }
963

    
964
        private void fillRow(Object[] row, DGNElemCore elemento, DGNElemComplexHeader parentElement, DGNReader dgnReader) {
965
            row[ID_FIELD_HEIGHTTEXT] = new Double(0);
966
            row[ID_FIELD_HEIGHTTEXTRAW] = new Double(0);
967
            row[ID_FIELD_ROTATIONTEXT] = new Double(0);
968
            row[ID_FIELD_TEXT] = null;
969

    
970
            row[ID_FIELD_ID] = elemento.getID();
971
            row[ID_FIELD_TYPE] = elemento.getType();
972
            row[ID_FIELD_STYPE] = elemento.getSType();
973
            row[ID_FIELD_LEVEL] = elemento.getLevelAsString();
974
            row[ID_FIELD_COLOR] = elemento.getColor();
975
            row[ID_FIELD_FILLCOLOR] = elemento.getShapeFillColor();
976
            row[ID_FIELD_ENTITY] = elemento.getEntityName();
977
            row[ID_FIELD_STYLE] = elemento.getStyle();
978
            row[ID_FIELD_WEIGHT] = elemento.getWeight();
979
            row[ID_FIELD_GROUP] = elemento.getGroup();
980
            row[ID_FIELD_ELEVATION] = elemento.getElevation();
981
            row[ID_FIELD_ISCOMPLEXSHAPEHEADER] = elemento.isComplexShapeHeader();
982
            row[ID_FIELD_ISSHAPE] = elemento.isShape();
983
            row[ID_FIELD_ISHOLE] = elemento.isHole();
984
            row[ID_FIELD_ISCOMPLEX] = elemento.isComplex();
985
            row[ID_FIELD_PARENT] = elemento.getID();
986
            if (parentElement != null) {
987
                row[ID_FIELD_PARENT] = parentElement.getID();
988
            }
989
            row[ID_FIELD_SCALE] = dgnReader.getInfo().scale;
990

    
991
        }
992

    
993
        public void load_group1() throws DataException, CreateEnvelopeException {
994

    
995
            this.envelope = null;
996

    
997
            FileWriter xmlfw = null;
998
            BufferedWriter xmlbfw = null;
999
            if (getDGNParameters().getXMLFile() != null) {
1000
                File xmlfile = getDGNParameters().getXMLFile();
1001
                try {
1002
                    xmlfw = new FileWriter(xmlfile);
1003
                    xmlbfw = new BufferedWriter(xmlfw);
1004

    
1005
                } catch (Exception ex) {
1006
                    xmlfw = null;
1007
                    xmlbfw = null;
1008
                    logger.warn("Can't open xmfile for output ("+ xmlfile.getAbsolutePath() + "'.", ex);
1009
                }
1010
                if (xmlbfw != null) {
1011
                    try {
1012
                        xmlbfw.write("<DGN>\n");
1013
                    } catch (IOException ex) {
1014
                        logger.warn("Can't write to the xml file.", ex);
1015
                    }
1016
                }
1017

    
1018
            }
1019
            DGNReader dgnReader = new DGNReader(file.getAbsolutePath());
1020
            if (dgnReader.getInfo().dimension == 2) {
1021
                envelope = geomManager.createEnvelope(Geometry.SUBTYPES.GEOM2D);
1022
            } else {
1023
                envelope = geomManager.createEnvelope(SUBTYPES.GEOM3D);
1024
            }
1025

    
1026
            FeatureType type = getDefaultType().getNotEditableCopy();
1027
            int fTypeSize = type.size();
1028
            Object[] auxRow = new Object[fTypeSize];
1029
            Object[] cellRow = new Object[fTypeSize];
1030
            Object[] complexRow = new Object[fTypeSize];
1031

    
1032
            boolean bElementoCompuesto = false;
1033
            boolean bEsPoligono = false;
1034
            boolean bInsideCell = false;
1035
            boolean bFirstHoleEntity = false;
1036
            boolean bConnect = false; // Se usa para que los pol?gonos cierren
1037
            // bien cuando son formas compuestas
1038
            // int contadorSubElementos = 0;
1039
            // int numSubElementos = 0;
1040
            int complex_index_fill_color = -1;
1041
            int nClass; // Para filtrar los elementos de construcci?n, etc.
1042
            GeneralPathX elementoCompuesto = new GeneralPathX(GeneralPathX.WIND_EVEN_ODD);
1043

    
1044
            int counterOfElement = 0;
1045
            DGNElemComplexHeader parentElement = null;
1046

    
1047
            for (int id = 0; id < dgnReader.getNumEntities(); id++) {
1048
                dgnReader.DGNGotoElement(id);
1049

    
1050
                DGNElemCore elemento = dgnReader.DGNReadElement();
1051
                if (parentElement != null) {
1052
                    counterOfElement++;
1053
                    if (parentElement.getNumElements() < counterOfElement) {
1054
                        // Ya hemos terminado de recorrer el elemento complejo.
1055
                        parentElement = null;
1056
                    }
1057
                }
1058

    
1059
                if (xmlbfw != null && elemento != null) {
1060
                    // Volcamos el elemnto del DGN a fichero en formato XML
1061
                    try {
1062
                        xmlbfw.write(dgnReader.DGNDumpElement(dgnReader.getInfo(), elemento));
1063
                    } catch (IOException ex) {
1064
                        logger.warn("Can't write to the xml file.", ex);
1065
                    }
1066
                }
1067

    
1068
                nClass = 0;
1069
                fillRow(auxRow, elemento, parentElement, dgnReader);
1070
                auxRow[ID_FIELD_HEIGHTTEXT] = new Double(0);
1071
                auxRow[ID_FIELD_ROTATIONTEXT] = new Double(0);
1072
                auxRow[ID_FIELD_TEXT] = null;
1073

    
1074
                if (elemento.properties != 0) {
1075
                    nClass = elemento.properties & DGNFileHeader.DGNPF_CLASS;
1076
                }
1077

    
1078
                if ((elemento != null) && (elemento.deleted == 0)
1079
                    && (nClass == 0)) // Leer un elemento
1080
                {
1081

    
1082
                    // if ((elemento.element_id > 3800) && (elemento.element_id < 3850))
1083
                    // dgnReader.DGNDumpElement(dgnReader.getInfo(),elemento,"");
1084
                    if ((elemento.stype == DGNFileHeader.DGNST_MULTIPOINT)
1085
                        || (elemento.stype == DGNFileHeader.DGNST_ARC)
1086
                        || (elemento.stype == DGNFileHeader.DGNST_CELL_HEADER)
1087
                        || (elemento.stype == DGNFileHeader.DGNST_SHARED_CELL_DEFN)
1088
                        || (elemento.stype == DGNFileHeader.DGNST_COMPLEX_HEADER)) {
1089
                        if (elemento.complex != 0) {
1090
                            bElementoCompuesto = true;
1091
                        } else {
1092
                            if (bElementoCompuesto) {
1093
                                if (bInsideCell) {
1094
                                    auxRow[ID_FIELD_ENTITY] = cellRow[ID_FIELD_ENTITY];
1095
                                } else {
1096
                                    auxRow = complexRow;
1097
                                }
1098
                                addShape(createMultiCurve(elementoCompuesto),
1099
                                    auxRow, type, dgnReader);
1100

    
1101
                                if (bEsPoligono) {
1102
                                    if (complex_index_fill_color != -1) {
1103
                                        auxRow[ID_FIELD_COLOR] = complex_index_fill_color;
1104
                                    }
1105

    
1106
                                    addShape(
1107
                                        createMultiSurface(elementoCompuesto),
1108
                                        auxRow, type, dgnReader);
1109
                                }
1110

    
1111
                                elementoCompuesto = new GeneralPathX(GeneralPathX.WIND_EVEN_ODD);
1112
                            }
1113

    
1114
                            bElementoCompuesto = false;
1115
                            bEsPoligono = false;
1116
                            bConnect = false;
1117

    
1118
                            bInsideCell = false;
1119
                        }
1120
                    }
1121

    
1122
                    switch (elemento.stype) {
1123
                    case DGNFileHeader.DGNST_SHARED_CELL_DEFN:
1124
                        bInsideCell = true;
1125
                        fillRow(cellRow, elemento, parentElement, dgnReader);
1126
                        cellRow[ID_FIELD_ID] = elemento.element_id;
1127
                        cellRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1128
                        cellRow[ID_FIELD_COLOR] = elemento.color;
1129
                        cellRow[ID_FIELD_ENTITY] = "Shared Cell";
1130

    
1131
                        break;
1132

    
1133
                    case DGNFileHeader.DGNST_CELL_HEADER:
1134
                        bInsideCell = true;
1135

    
1136
                        fillRow(cellRow, elemento, parentElement, dgnReader);
1137
                        cellRow[ID_FIELD_ID] = elemento.element_id;
1138
                        cellRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1139
                        cellRow[ID_FIELD_COLOR] = elemento.color;
1140
                        cellRow[ID_FIELD_ENTITY] = "Cell";
1141
                        complex_index_fill_color = dgnReader.DGNGetShapeFillInfo(elemento);
1142
                        break;
1143

    
1144
                    case DGNFileHeader.DGNST_COMPLEX_HEADER:
1145

    
1146
                        // bElementoCompuesto = true;
1147
                        // contadorSubElementos = 0;
1148
                        DGNElemComplexHeader psComplexHeader = (DGNElemComplexHeader) elemento;
1149

    
1150
                        parentElement = psComplexHeader;
1151
                        counterOfElement = parentElement.getNumElements();
1152

    
1153
                        // numSubElementos = psComplexHeader.numelems;
1154
                        fillRow(complexRow, elemento, parentElement, dgnReader);
1155
                        complexRow[ID_FIELD_ID] = elemento.element_id;
1156
                        complexRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1157
                        complexRow[ID_FIELD_COLOR] = elemento.color;
1158
                        complexRow[ID_FIELD_ENTITY] = "Complex";
1159

    
1160
                        if (psComplexHeader.type == DGNFileHeader.DGNT_COMPLEX_SHAPE_HEADER) {
1161
                            bEsPoligono = true;
1162

    
1163
                            // Si es un agujero, no conectamos con el anterior
1164
                            if ((psComplexHeader.properties & 0x8000) != 0) {
1165
                                bFirstHoleEntity = true;
1166
                            } else {
1167
                                // Miramos si tiene color de relleno
1168
                                // complex_index_fill_color = -1;
1169
                                // if (elemento.attr_bytes > 0) {
1170
                                complex_index_fill_color = dgnReader.DGNGetShapeFillInfo(elemento);
1171

    
1172
                                // }
1173
                            }
1174

    
1175
                            bConnect = true;
1176
                        } else {
1177
                            bEsPoligono = false;
1178
                            bConnect = false;
1179
                        }
1180

    
1181
                        break;
1182

    
1183
                    case DGNFileHeader.DGNST_MULTIPOINT:
1184

    
1185
                        // OJO: Si lo que viene en este multipoint es un
1186
                        // elemento con type=11 (curve), se trata de una "parametric
1187
                        // spline curve". La vamos a tratar como si no fuera
1188
                        // curva, pero seg?n la documentaci?n, los 2 primeros puntos
1189
                        // y los 2 ?ltimos puntos definen "endpoint derivatives" y NO se muestran.
1190
                        // TODAV?A HAY UN PEQUE?O FALLO CON EL FICHERO
1191
                        // dgn-sample.dgn, pero lo dejo por ahora.
1192
                        // Es posible que tenga que ver con lo de los arcos
1193
                        // (arco distorsionado), que todav?a no est? metido.
1194
                        DGNElemMultiPoint psLine = (DGNElemMultiPoint) elemento;
1195
                        fillRow(auxRow, elemento, parentElement, dgnReader);
1196
                        auxRow[ID_FIELD_ID] = elemento.element_id;
1197
                        auxRow[ID_FIELD_ENTITY] = "Multipoint";
1198
                        auxRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1199
                        auxRow[ID_FIELD_COLOR] = elemento.color;
1200

    
1201
                        if ((psLine.num_vertices == 2)
1202
                            && (psLine.vertices[0].x == psLine.vertices[1].x)
1203
                            && (psLine.vertices[0].y == psLine.vertices[1].y)) {
1204
                            auxRow[ID_FIELD_ENTITY] = "Point";
1205
                            addShape(
1206
                                createPoint(psLine.vertices[0].x,
1207
                                    psLine.vertices[0].y,
1208
                                    psLine.vertices[0].z),
1209
                                auxRow,
1210
                                type, dgnReader);
1211
                        } else {
1212
                            GeneralPathX elShape = new GeneralPathX(GeneralPathX.WIND_EVEN_ODD);
1213

    
1214
                            if (psLine.type == DGNFileHeader.DGNT_CURVE) {
1215
                                psLine.num_vertices = psLine.num_vertices - 4;
1216

    
1217
                                for (int aux_n = 0; aux_n < psLine.num_vertices; aux_n++) {
1218
                                    psLine.vertices[aux_n] = psLine.vertices[aux_n + 2];
1219
                                }
1220
                            }
1221

    
1222
                            if ((psLine.type == DGNFileHeader.DGNT_SHAPE)
1223
                                && ((psLine.properties & 0x8000) != 0)) {
1224
                                // Invertimos el orden porque es un agujero
1225
                                elShape.moveTo(
1226
                                    psLine.vertices[psLine.num_vertices - 1].x,
1227
                                    psLine.vertices[psLine.num_vertices - 1].y);
1228

    
1229
                                for (int i = psLine.num_vertices - 2; i >= 0; i--) {
1230
                                    elShape.lineTo(psLine.vertices[i].x,
1231
                                        psLine.vertices[i].y);
1232
                                }
1233
                            } else {
1234
                                elShape.moveTo(psLine.vertices[0].x,
1235
                                    psLine.vertices[0].y);
1236

    
1237
                                for (int i = 1; i < psLine.num_vertices; i++) {
1238
                                    elShape.lineTo(psLine.vertices[i].x,
1239
                                        psLine.vertices[i].y);
1240
                                }
1241
                            }
1242

    
1243
                            if ((psLine.vertices[0].x == psLine.vertices[psLine.num_vertices - 1].x)
1244
                                && (psLine.vertices[0].y == psLine.vertices[psLine.num_vertices - 1].y)) {
1245
                                // Lo a?adimos tambi?n como pol?gono
1246
                                bEsPoligono = true;
1247

    
1248
                                // Miramos si tiene color de relleno
1249
                                if (elemento.attr_bytes > 0) {
1250
                                    elemento.color = dgnReader.DGNGetShapeFillInfo(elemento);
1251

    
1252
                                    if (elemento.color != -1) {
1253
                                        auxRow[ID_FIELD_COLOR] = elemento.color;
1254
                                    }
1255
                                }
1256

    
1257
                                if (elemento.complex == 0) {
1258
                                    addShape(createSurface(elShape), auxRow,
1259
                                        type, dgnReader);
1260
                                }
1261
                            }
1262

    
1263
                            if (elemento.complex != 0) {
1264
                                // Si es un agujero o
1265
                                // es la primera entidad del agujero, lo
1266
                                // a?adimos sin unir al anterior
1267
                                if (bFirstHoleEntity
1268
                                    || ((psLine.type == DGNFileHeader.DGNT_SHAPE) && ((psLine.properties & 0x8000) != 0))) {
1269
                                    elementoCompuesto.append(elShape.getPathIterator(null), false);
1270
                                    bFirstHoleEntity = false;
1271
                                } else {
1272
                                    elementoCompuesto.append(elShape.getPathIterator(null),
1273
                                            bConnect);
1274
                                }
1275
                            } else {
1276
                                addShape(createMultiCurve(elShape), auxRow,
1277
                                    type, dgnReader);
1278
                            }
1279
                        }
1280

    
1281
                        break;
1282

    
1283
                    case DGNFileHeader.DGNST_ARC:
1284

    
1285
                        // dgnReader.DGNDumpElement(dgnReader.getInfo(),
1286
                        // elemento,"");
1287
                        DGNElemArc psArc = (DGNElemArc) elemento;
1288

    
1289
                        // La definici?n de arco de MicroStation es distinta a
1290
                        // la de Java.
1291
                        // En el dgn el origin se entiende que es el centro del
1292
                        // arco,
1293
                        // y a la hora de crear un Arc2D las 2 primeras
1294
                        // coordenadas son
1295
                        // la esquina inferior izquierda del rect?ngulo que
1296
                        // rodea al arco.
1297
                        // 1.- Creamos la elipse sin rotaci?n.
1298
                        // 2.- Creamos el arco
1299
                        // 3.- Rotamos el resultado
1300
                        AffineTransform mT = AffineTransform.getRotateInstance(
1301
                                Math.toRadians(psArc.rotation), psArc.origin.x,
1302
                                psArc.origin.y);
1303

    
1304
                        // mT.preConcatenate(AffineTransform.getScaleInstance(100.0,100.0));
1305
                        Arc2D.Double elArco = new Arc2D.Double(psArc.origin.x
1306
                                - psArc.primary_axis, psArc.origin.y
1307
                                - psArc.secondary_axis,
1308
                                2.0 * psArc.primary_axis,
1309
                                2.0 * psArc.secondary_axis, -psArc.startang,
1310
                                -psArc.sweepang, Arc2D.OPEN);
1311

    
1312
                        // Ellipse2D.Double elArco = new
1313
                        // Ellipse2D.Double(psArc.origin.x - psArc.primary_axis,
1314
                        // psArc.origin.y - psArc.secondary_axis,2.0 *
1315
                        // psArc.primary_axis, 2.0 * psArc.secondary_axis);
1316
                        GeneralPathX elShapeArc = new GeneralPathX(elArco.getPathIterator(null));
1317

    
1318
                        // Transformamos el GeneralPahtX porque si transformamos
1319
                        // elArco nos lo convierte
1320
                        // a GeneralPath y nos guarda las coordenadas en float,
1321
                        // con la correspondiente p?rdida de precisi?n
1322
                        elShapeArc.transform(mT);
1323

    
1324
                        if (dgnReader.getInfo().dimension == 3) {
1325
                            // Aqu? podr?amos hacer cosas con la coordenada Z
1326
                        }
1327

    
1328
                        fillRow(auxRow, elemento, parentElement, dgnReader);
1329
                        auxRow[ID_FIELD_ID] = elemento.element_id;
1330
                        auxRow[ID_FIELD_ENTITY] = "Arc";
1331
                        auxRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1332
                        auxRow[ID_FIELD_COLOR] = elemento.color;
1333

    
1334
                        /*
1335
                         * Line2D.Double ejeMayor = new
1336
                         * Line2D.Double(psArc.origin.x - psArc.primary_axis,
1337
                         * psArc.origin.y, psArc.origin.x + psArc.primary_axis,
1338
                         * psArc.origin.y);
1339
                         *
1340
                         * lyrLines.addShape(new
1341
                         * FShape(FConstant.SHAPE_TYPE_POLYLINE, new
1342
                         * GeneralPathX(ejeMayor)), auxRow);
1343
                         */
1344
                        // lyrLines.addShape(new
1345
                        // FShape(FConstant.SHAPE_TYPE_POLYLINE, elShapeArc),
1346
                        // auxRow);
1347
                        if (elemento.complex != 0) {
1348
                            // Esto es una posible fuente de fallos si detr?s de
1349
                            // una elipse vienen m?s cosas pegadas. Deber?amos
1350
                            // volver a conectar una vez pasada la elipse.
1351
                            if (elemento.type == DGNFileHeader.DGNT_ELLIPSE) {
1352
                                bConnect = false;
1353
                            }
1354

    
1355
                            // SI LA ELIPSE ES UN AGUJERO, SE A?ADE SIN PEGAR
1356
                            // Y EL ELEMENTO ES UN POLIGONO
1357
                            if (bFirstHoleEntity
1358
                                || ((elemento.type == DGNFileHeader.DGNT_SHAPE) && ((elemento.properties & 0x8000) != 0))) {
1359
                                elementoCompuesto.append(elShapeArc.getPathIterator(null), false);
1360
                                bFirstHoleEntity = false;
1361
                            } else {
1362
                                elementoCompuesto.append(elShapeArc.getPathIterator(null), bConnect);
1363
                            }
1364
                        } else {
1365
                            addShape(createMultiCurve(elShapeArc), auxRow,
1366
                                type, dgnReader);
1367

    
1368
                            if (psArc.type == DGNFileHeader.DGNT_ELLIPSE) {
1369
                                addShape(createSurface(elShapeArc), auxRow,
1370
                                    type, dgnReader);
1371
                            }
1372
                        }
1373

    
1374
                        break;
1375

    
1376
                    case DGNFileHeader.DGNST_TEXT:
1377

    
1378
                        DGNElemText psText = (DGNElemText) elemento;
1379
                        Geometry elShapeTxt = createPoint(psText.origin.x, psText.origin.y,
1380
                                psText.origin.z);
1381

    
1382
                        fillRow(auxRow, elemento, parentElement, dgnReader);
1383
                        auxRow[ID_FIELD_ID] = elemento.element_id;
1384
                        auxRow[ID_FIELD_ENTITY] = "Text";
1385
                        auxRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1386
                        auxRow[ID_FIELD_COLOR] = elemento.color;
1387
                        auxRow[ID_FIELD_HEIGHTTEXT] = psText.height_mult;
1388
                        auxRow[ID_FIELD_HEIGHTTEXTRAW] = psText.height_raw;
1389
                        auxRow[ID_FIELD_ROTATIONTEXT] = psText.rotation;
1390
                        auxRow[ID_FIELD_TEXT] = psText.string; // .trim();
1391
                        addShape(elShapeTxt, auxRow, type, dgnReader);
1392
                        break;
1393

    
1394
                    /*
1395
                     * default:
1396
                     * dgnReader.DGNDumpElement(dgnReader.getInfo(),
1397
                     * elemento, "");
1398
                     */
1399
                    } // switch
1400
                } // if
1401
            } // for
1402

    
1403
            if (bElementoCompuesto) {
1404
                if (bInsideCell) {
1405
                    auxRow = cellRow;
1406
                } else {
1407
                    auxRow = complexRow;
1408
                }
1409

    
1410
                addShape(createMultiCurve(elementoCompuesto), auxRow, type,
1411
                    dgnReader);
1412

    
1413
                if (bEsPoligono) {
1414
                    if (complex_index_fill_color != -1) {
1415
                        auxRow[ID_FIELD_COLOR] = complex_index_fill_color;
1416
                    }
1417

    
1418
                    addShape(createSurface(elementoCompuesto), auxRow, type,
1419
                        dgnReader);
1420
                }
1421
            }
1422

    
1423
            if (xmlbfw != null) {
1424
                try {
1425
                    xmlbfw.write("</DGN>\n");
1426
                } catch (IOException ex) {
1427
                    logger.warn("Can't write to the xml file.", ex);
1428
                }
1429
                IOUtils.closeQuietly(xmlbfw);
1430
                IOUtils.closeQuietly(xmlfw);
1431
            }
1432

    
1433
        }
1434

    
1435
        private Geometry createMultiSurface(GeneralPathX elementoCompuesto)
1436
            throws DataException {
1437
            try {
1438
                return geomManager.createMultiSurface(elementoCompuesto,
1439
                    SUBTYPES.GEOM2D);
1440
            } catch (CreateGeometryException e) {
1441
                throw new org.gvsig.fmap.dal.feature.exception.CreateGeometryException(
1442
                    e);
1443
            }
1444

    
1445
        }
1446

    
1447
        private Point createPoint(double x, double y, double z)
1448
            throws DataException {
1449
            Point point;
1450
            int subtype = SUBTYPES.GEOM3D;
1451
            if (getDGNParameters().force2D()) {
1452
                subtype = SUBTYPES.GEOM2D;
1453
            }
1454
            try {
1455
                point = (Point) geomManager.create(TYPES.POINT, subtype);
1456
            } catch (CreateGeometryException e) {
1457
                throw new org.gvsig.fmap.dal.feature.exception.CreateGeometryException(e);
1458
            }
1459
            point.setCoordinates(new double[] { x, y, z });
1460

    
1461
            return point;
1462
        }
1463

    
1464
        private void addFeature(FeatureProvider data, DGNReader dgnReader) throws DataException {
1465

    
1466
            addFeatureProvider(data);
1467
            Geometry geometry = data.getDefaultGeometry();
1468
            if (geometry != null) {
1469
                if (this.envelope == null) {
1470
                    this.envelope = geometry.getEnvelope();
1471
                } else {
1472
                    this.envelope.add(geometry.getEnvelope());
1473
                }
1474
            }
1475
            if (this.leyendBuilder != null) {
1476
                this.leyendBuilder.process(data, dgnReader);
1477
            }
1478
        }
1479

    
1480
        private void addShape(Geometry geometry, Object[] auxRow,
1481
            FeatureType type, DGNReader dgnReader) throws DataException {
1482

    
1483
            FeatureProvider data = createFeatureProvider(type);
1484
            for (int i = 0; i < type.size(); i++) {
1485
                data.set(i, auxRow[i]);
1486
            }
1487
            data.setDefaultGeometry(geometry);
1488
            addFeature(data, dgnReader);
1489
        }
1490

    
1491
        private Geometry createMultiCurve(GeneralPathX elementoCompuesto)
1492
            throws DataException {
1493
            try {
1494
                return geomManager.createMultiCurve(elementoCompuesto,
1495
                    SUBTYPES.GEOM2D);
1496
            } catch (CreateGeometryException e) {
1497
                throw new org.gvsig.fmap.dal.feature.exception.CreateGeometryException(
1498
                    e);
1499
            }
1500
        }
1501

    
1502
        private Geometry createSurface(GeneralPathX elementoCompuesto)
1503
            throws DataException {
1504
            try {
1505
                return geomManager.createCurve(elementoCompuesto,
1506
                    SUBTYPES.GEOM2D);
1507
            } catch (CreateGeometryException e) {
1508
                throw new org.gvsig.fmap.dal.feature.exception.CreateGeometryException(
1509
                    e);
1510
            }
1511

    
1512
        }
1513

    
1514
    }
1515

    
1516
    public boolean closeResourceRequested(ResourceProvider resource) {
1517
        return true;
1518
    }
1519

    
1520
    public int getOIDType() {
1521
        return DataTypes.LONG;
1522
    }
1523

    
1524
    public boolean supportsAppendMode() {
1525
        return false;
1526
    }
1527

    
1528
    public void append(FeatureProvider featureProvider) {
1529
        throw new UnsupportedOperationException();
1530
    }
1531

    
1532
    public void beginAppend() {
1533
        throw new UnsupportedOperationException();
1534
    }
1535

    
1536
    public void endAppend() {
1537
        throw new UnsupportedOperationException();
1538
    }
1539

    
1540
    public Object createNewOID() {
1541
        return new Long(counterNewsOIDs++);
1542
    }
1543

    
1544
    protected void initializeFeatureTypes() throws InitializeException {
1545
        try {
1546
            this.open();
1547
        } catch (OpenException e) {
1548
            throw new InitializeException(this.getProviderName(), e);
1549
        }
1550
    }
1551

    
1552
    public Envelope getEnvelope() throws DataException {
1553
        this.open();
1554
        return (Envelope) this.getDynValue("Envelope");
1555
    }
1556

    
1557
    /*
1558
     * (non-Javadoc)
1559
     *
1560
     * @see
1561
     * org.gvsig.fmap.dal.resource.spi.ResourceConsumer#resourceChanged(org.
1562
     * gvsig.fmap.dal.resource.spi.ResourceProvider)
1563
     */
1564
    public void resourceChanged(ResourceProvider resource) {
1565
        this.getStoreServices().notifyChange(
1566
            DataStoreNotification.RESOURCE_CHANGED, resource);
1567
    }
1568

    
1569
    public Object getSourceId() {
1570
        return this.getDGNParameters().getFile();
1571
    }
1572

    
1573
    public String getName() {
1574
        String name = this.getDGNParameters().getFile().getName();
1575
        int n = name.lastIndexOf(".");
1576
        if (n < 1) {
1577
            return name;
1578
        }
1579
        return name.substring(0, n);
1580
    }
1581

    
1582
    public String getFullName() {
1583
        return this.getDGNParameters().getFile().getAbsolutePath();
1584
    }
1585

    
1586
    public ResourceProvider getResource() {
1587
        return resource;
1588
    }
1589

    
1590
    public static boolean equals(double a, double b, double precision) {
1591
        double v = Math.abs(a - b);
1592
        return v < precision;
1593
    }
1594
}