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 @ 163

History | View | Annotate | Download (68.5 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.IGeneralPathX;
69
import org.gvsig.fmap.geom.primitive.OrientablePrimitive;
70
import org.gvsig.fmap.geom.primitive.Point;
71
import org.gvsig.fmap.geom.type.GeometryType;
72
import org.gvsig.tools.dynobject.exception.DynMethodException;
73

    
74
public class DGNStoreProvider extends AbstractMemoryStoreProvider implements
75
    ResourceConsumer {
76

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

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

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

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

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

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

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

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

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

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

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

    
183
    DGNData dgndata = null;
184

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

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

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

    
198
        resource.addConsumer(this);
199

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

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

    
212
        this.initializeFeatureTypes();
213

    
214
    }
215

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

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

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

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

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

    
245
    private class DGNData {
246

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

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

    
267
    public static class TimeCounter {
268

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

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

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

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

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

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

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

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

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

    
314
                public Object run() throws Exception {
315

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

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

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

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

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

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

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

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

    
414
    }
415

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

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

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

    
431
        featureType.setHasOID(true);
432

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
555
        featureType.setDefaultGeometryAttributeName(NAME_FIELD_GEOMETRY);
556

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

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

    
562
        return types;
563
    }
564

    
565
    public class Reader {
566

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

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

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

    
590
        public void begin(FeatureStoreProviderServices storeProviderServices) {
591

    
592
            types = getFeatureTypes(storeProviderServices);
593

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

    
598
        }
599

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

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

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

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

    
626
        public void load_plain() throws DataException {
627

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

    
631
            this.envelope = null;
632

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

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

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

    
658
                }
659

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

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

    
668
                envelope = geomManager.createEnvelope(subtypeHeader);
669

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

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

    
675
                double zvalue = 0;
676

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

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

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

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

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

    
707
                    FeatureProvider data = createFeatureProvider(type);
708

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

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

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

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

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

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

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

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

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

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

    
837
                            PathIterator pathIterator = elArco.getPathIterator(null);
838
                            IGeneralPathX elShapeArc = geomManager.createGeneralPath(
839
                                    IGeneralPathX.WIND_EVEN_ODD, 
840
                                    pathIterator
841
                            );
842

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

    
848
                            OrientablePrimitive geom = null;
849
                            subtypeElement = getGeometrySubType(dgnarc);
850
                            if (dgnarc.isSurface()) {
851
                                geom = geomManager.createPolygon(subtypeElement);
852
                            } else {
853
                                geom = geomManager.createLine(subtypeElement);
854
                            }
855
                            for( int i=0; i<elShapeArc.getNumCoords(); i++ ) {
856
                                double[] coords = elShapeArc.getCoordinatesAt(i);
857
                                if( ignoreZs || subtypeElement == Geometry.SUBTYPES.GEOM2D || subtypeElement == Geometry.SUBTYPES.GEOM2DM) {
858
                                    geom.addVertex(coords[0], coords[1]);
859
                                } else {
860
                                    geom.addVertex(coords[0], coords[1],zvalue);
861
                                }
862
                                
863
                            }
864
                            data.setDefaultGeometry(geom);
865
                            break;
866

    
867
                        case DGNFileHeader.DGNST_TEXT:
868
                            DGNElemText dgntext = (DGNElemText) elemento;
869
                            subtypeElement = getGeometrySubType(dgntext);
870
                            Point point = (Point) geomManager.create(TYPES.POINT, subtypeHeader);
871
                            point.setCoordinateAt(0, dgntext.getPoint().getX());
872
                            point.setCoordinateAt(1, dgntext.getPoint().getY());
873
                            if (subtypeHeader == Geometry.SUBTYPES.GEOM3D) {
874
                                if(subtypeElement == Geometry.SUBTYPES.GEOM2D){
875
                                    point.setCoordinateAt(2, 0);
876
                                } else {
877
                                    point.setCoordinateAt(2, dgntext.getPoint().getZ());
878
                                }
879
                            }
880

    
881
                            data.set(ID_FIELD_HEIGHTTEXT, dgntext.getHeight());
882
                            data.set(ID_FIELD_HEIGHTTEXTRAW,dgntext.getRawHeight());
883
                            data.set(ID_FIELD_ROTATIONTEXT,dgntext.getRotation());
884
                            data.set(ID_FIELD_TEXT, dgntext.getText());
885

    
886
                            data.setDefaultGeometry(point);
887
                            break;
888

    
889
                        default:
890
                            break;
891

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

    
908
                EditableFeatureType featureType = storeProvider.getFeatureStore().getDefaultFeatureType().getCopy().getEditable();
909
                FeatureAttributeDescriptor fad = (FeatureAttributeDescriptor) featureType.get(featureType.getDefaultGeometryAttributeName());
910
                featureType.remove(fad.getName());
911
                EditableFeatureAttributeDescriptor efad = featureType.add(fad.getName(), fad.getType(), fad.getSize());
912
                efad.setDefaultValue(fad.getDefaultValue());
913

    
914
                GeometryType gty = null;
915

    
916
                gty = geomManager.getGeometryType(fad.getGeomType().getType(),subtypeHeader);
917

    
918
                efad.setGeometryType(gty);
919

    
920
                efad.setPrecision(fad.getPrecision());
921
                featureType.setDefaultGeometryAttributeName(fad.getName());
922

    
923

    
924
                if (xmlbfw != null) {
925
                    try {
926
                        xmlbfw.write("</DGN>\n");
927
                    } catch (IOException ex) {
928
                        logger.warn("Can't write to the xml file.", ex);
929
                    }
930
                }
931

    
932
            } catch (Exception ex) {
933
                logger.warn("Can't process DGN file '" + getFullName() + ".",ex);
934
                throw new LoadException(ex, getFullName());
935
            } finally {
936
                IOUtils.closeQuietly(xmlbfw);
937
                IOUtils.closeQuietly(xmlfw);
938
            }
939

    
940
        }
941

    
942
        private int getGeometrySubType(DGNElemCore element) {
943
            if (getDGNParameters().force2D()) {
944
                return Geometry.SUBTYPES.GEOM2D;
945
            }
946
            return element.is3D() ? Geometry.SUBTYPES.GEOM3D : Geometry.SUBTYPES.GEOM2D;
947
        }
948

    
949
        private void addVertex(OrientablePrimitive geom, double x, double y,
950
            double z) {
951
            if (geom.getDimension() == 2) {
952
                geom.addVertex(x, y);
953
            } else {
954
                geom.addVertex(x, y, z);
955
            }
956
        }
957

    
958
        private void fillRow(Object[] row, DGNElemCore elemento, DGNElemComplexHeader parentElement, DGNReader dgnReader) {
959
            row[ID_FIELD_HEIGHTTEXT] = new Double(0);
960
            row[ID_FIELD_HEIGHTTEXTRAW] = new Double(0);
961
            row[ID_FIELD_ROTATIONTEXT] = new Double(0);
962
            row[ID_FIELD_TEXT] = null;
963

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

    
985
        }
986

    
987
        public void load_group1() throws DataException, CreateEnvelopeException {
988

    
989
            this.envelope = null;
990

    
991
            FileWriter xmlfw = null;
992
            BufferedWriter xmlbfw = null;
993
            if (getDGNParameters().getXMLFile() != null) {
994
                File xmlfile = getDGNParameters().getXMLFile();
995
                try {
996
                    xmlfw = new FileWriter(xmlfile);
997
                    xmlbfw = new BufferedWriter(xmlfw);
998

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

    
1012
            }
1013
            DGNReader dgnReader = new DGNReader(file.getAbsolutePath());
1014
            if (dgnReader.getInfo().dimension == 2) {
1015
                envelope = geomManager.createEnvelope(Geometry.SUBTYPES.GEOM2D);
1016
            } else {
1017
                envelope = geomManager.createEnvelope(SUBTYPES.GEOM3D);
1018
            }
1019

    
1020
            FeatureType type = getDefaultType().getNotEditableCopy();
1021
            int fTypeSize = type.size();
1022
            Object[] auxRow = new Object[fTypeSize];
1023
            Object[] cellRow = new Object[fTypeSize];
1024
            Object[] complexRow = new Object[fTypeSize];
1025

    
1026
            boolean bElementoCompuesto = false;
1027
            boolean bEsPoligono = false;
1028
            boolean bInsideCell = false;
1029
            boolean bFirstHoleEntity = false;
1030
            boolean bConnect = false; // Se usa para que los pol?gonos cierren
1031
            // bien cuando son formas compuestas
1032
            // int contadorSubElementos = 0;
1033
            // int numSubElementos = 0;
1034
            int complex_index_fill_color = -1;
1035
            int nClass; // Para filtrar los elementos de construcci?n, etc.
1036
            IGeneralPathX elementoCompuesto = geomManager.createGeneralPath(IGeneralPathX.WIND_EVEN_ODD, null);
1037

    
1038
            int counterOfElement = 0;
1039
            DGNElemComplexHeader parentElement = null;
1040

    
1041
            for (int id = 0; id < dgnReader.getNumEntities(); id++) {
1042
                dgnReader.DGNGotoElement(id);
1043

    
1044
                DGNElemCore elemento = dgnReader.DGNReadElement();
1045
                if (parentElement != null) {
1046
                    counterOfElement++;
1047
                    if (parentElement.getNumElements() < counterOfElement) {
1048
                        // Ya hemos terminado de recorrer el elemento complejo.
1049
                        parentElement = null;
1050
                    }
1051
                }
1052

    
1053
                if (xmlbfw != null && elemento != null) {
1054
                    // Volcamos el elemnto del DGN a fichero en formato XML
1055
                    try {
1056
                        xmlbfw.write(dgnReader.DGNDumpElement(dgnReader.getInfo(), elemento));
1057
                    } catch (IOException ex) {
1058
                        logger.warn("Can't write to the xml file.", ex);
1059
                    }
1060
                }
1061

    
1062
                nClass = 0;
1063
                fillRow(auxRow, elemento, parentElement, dgnReader);
1064
                auxRow[ID_FIELD_HEIGHTTEXT] = new Double(0);
1065
                auxRow[ID_FIELD_ROTATIONTEXT] = new Double(0);
1066
                auxRow[ID_FIELD_TEXT] = null;
1067

    
1068
                if (elemento.properties != 0) {
1069
                    nClass = elemento.properties & DGNFileHeader.DGNPF_CLASS;
1070
                }
1071

    
1072
                if ((elemento != null) && (elemento.deleted == 0)
1073
                    && (nClass == 0)) // Leer un elemento
1074
                {
1075

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

    
1095
                                if (bEsPoligono) {
1096
                                    if (complex_index_fill_color != -1) {
1097
                                        auxRow[ID_FIELD_COLOR] = complex_index_fill_color;
1098
                                    }
1099

    
1100
                                    addShape(
1101
                                        createMultiSurface(elementoCompuesto),
1102
                                        auxRow, type, dgnReader);
1103
                                }
1104

    
1105
                                elementoCompuesto = geomManager.createGeneralPath(IGeneralPathX.WIND_EVEN_ODD, null);
1106
                            }
1107

    
1108
                            bElementoCompuesto = false;
1109
                            bEsPoligono = false;
1110
                            bConnect = false;
1111

    
1112
                            bInsideCell = false;
1113
                        }
1114
                    }
1115

    
1116
                    switch (elemento.stype) {
1117
                    case DGNFileHeader.DGNST_SHARED_CELL_DEFN:
1118
                        bInsideCell = true;
1119
                        fillRow(cellRow, elemento, parentElement, dgnReader);
1120
                        cellRow[ID_FIELD_ID] = elemento.element_id;
1121
                        cellRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1122
                        cellRow[ID_FIELD_COLOR] = elemento.color;
1123
                        cellRow[ID_FIELD_ENTITY] = "Shared Cell";
1124

    
1125
                        break;
1126

    
1127
                    case DGNFileHeader.DGNST_CELL_HEADER:
1128
                        bInsideCell = true;
1129

    
1130
                        fillRow(cellRow, elemento, parentElement, dgnReader);
1131
                        cellRow[ID_FIELD_ID] = elemento.element_id;
1132
                        cellRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1133
                        cellRow[ID_FIELD_COLOR] = elemento.color;
1134
                        cellRow[ID_FIELD_ENTITY] = "Cell";
1135
                        complex_index_fill_color = dgnReader.DGNGetShapeFillInfo(elemento);
1136
                        break;
1137

    
1138
                    case DGNFileHeader.DGNST_COMPLEX_HEADER:
1139

    
1140
                        // bElementoCompuesto = true;
1141
                        // contadorSubElementos = 0;
1142
                        DGNElemComplexHeader psComplexHeader = (DGNElemComplexHeader) elemento;
1143

    
1144
                        parentElement = psComplexHeader;
1145
                        counterOfElement = parentElement.getNumElements();
1146

    
1147
                        // numSubElementos = psComplexHeader.numelems;
1148
                        fillRow(complexRow, elemento, parentElement, dgnReader);
1149
                        complexRow[ID_FIELD_ID] = elemento.element_id;
1150
                        complexRow[ID_FIELD_LAYER] = String.valueOf(elemento.level);
1151
                        complexRow[ID_FIELD_COLOR] = elemento.color;
1152
                        complexRow[ID_FIELD_ENTITY] = "Complex";
1153

    
1154
                        if (psComplexHeader.type == DGNFileHeader.DGNT_COMPLEX_SHAPE_HEADER) {
1155
                            bEsPoligono = true;
1156

    
1157
                            // Si es un agujero, no conectamos con el anterior
1158
                            if ((psComplexHeader.properties & 0x8000) != 0) {
1159
                                bFirstHoleEntity = true;
1160
                            } else {
1161
                                // Miramos si tiene color de relleno
1162
                                // complex_index_fill_color = -1;
1163
                                // if (elemento.attr_bytes > 0) {
1164
                                complex_index_fill_color = dgnReader.DGNGetShapeFillInfo(elemento);
1165

    
1166
                                // }
1167
                            }
1168

    
1169
                            bConnect = true;
1170
                        } else {
1171
                            bEsPoligono = false;
1172
                            bConnect = false;
1173
                        }
1174

    
1175
                        break;
1176

    
1177
                    case DGNFileHeader.DGNST_MULTIPOINT:
1178

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

    
1195
                        if ((psLine.num_vertices == 2)
1196
                            && (psLine.vertices[0].x == psLine.vertices[1].x)
1197
                            && (psLine.vertices[0].y == psLine.vertices[1].y)) {
1198
                            auxRow[ID_FIELD_ENTITY] = "Point";
1199
                            addShape(
1200
                                createPoint(psLine.vertices[0].x,
1201
                                    psLine.vertices[0].y,
1202
                                    psLine.vertices[0].z),
1203
                                auxRow,
1204
                                type, dgnReader);
1205
                        } else {
1206
                            IGeneralPathX elShape = geomManager.createGeneralPath(IGeneralPathX.WIND_EVEN_ODD, null);
1207

    
1208
                            if (psLine.type == DGNFileHeader.DGNT_CURVE) {
1209
                                psLine.num_vertices = psLine.num_vertices - 4;
1210

    
1211
                                for (int aux_n = 0; aux_n < psLine.num_vertices; aux_n++) {
1212
                                    psLine.vertices[aux_n] = psLine.vertices[aux_n + 2];
1213
                                }
1214
                            }
1215

    
1216
                            if ((psLine.type == DGNFileHeader.DGNT_SHAPE)
1217
                                && ((psLine.properties & 0x8000) != 0)) {
1218
                                // Invertimos el orden porque es un agujero
1219
                                elShape.moveTo(
1220
                                    psLine.vertices[psLine.num_vertices - 1].x,
1221
                                    psLine.vertices[psLine.num_vertices - 1].y);
1222

    
1223
                                for (int i = psLine.num_vertices - 2; i >= 0; i--) {
1224
                                    elShape.lineTo(psLine.vertices[i].x,
1225
                                        psLine.vertices[i].y);
1226
                                }
1227
                            } else {
1228
                                elShape.moveTo(psLine.vertices[0].x,
1229
                                    psLine.vertices[0].y);
1230

    
1231
                                for (int i = 1; i < psLine.num_vertices; i++) {
1232
                                    elShape.lineTo(psLine.vertices[i].x,
1233
                                        psLine.vertices[i].y);
1234
                                }
1235
                            }
1236

    
1237
                            if ((psLine.vertices[0].x == psLine.vertices[psLine.num_vertices - 1].x)
1238
                                && (psLine.vertices[0].y == psLine.vertices[psLine.num_vertices - 1].y)) {
1239
                                // Lo a?adimos tambi?n como pol?gono
1240
                                bEsPoligono = true;
1241

    
1242
                                // Miramos si tiene color de relleno
1243
                                if (elemento.attr_bytes > 0) {
1244
                                    elemento.color = dgnReader.DGNGetShapeFillInfo(elemento);
1245

    
1246
                                    if (elemento.color != -1) {
1247
                                        auxRow[ID_FIELD_COLOR] = elemento.color;
1248
                                    }
1249
                                }
1250

    
1251
                                if (elemento.complex == 0) {
1252
                                    addShape(createSurface(elShape), auxRow,
1253
                                        type, dgnReader);
1254
                                }
1255
                            }
1256

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

    
1275
                        break;
1276

    
1277
                    case DGNFileHeader.DGNST_ARC:
1278

    
1279
                        // dgnReader.DGNDumpElement(dgnReader.getInfo(),
1280
                        // elemento,"");
1281
                        DGNElemArc psArc = (DGNElemArc) elemento;
1282

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

    
1298
                        // mT.preConcatenate(AffineTransform.getScaleInstance(100.0,100.0));
1299
                        Arc2D.Double elArco = new Arc2D.Double(psArc.origin.x
1300
                                - psArc.primary_axis, psArc.origin.y
1301
                                - psArc.secondary_axis,
1302
                                2.0 * psArc.primary_axis,
1303
                                2.0 * psArc.secondary_axis, -psArc.startang,
1304
                                -psArc.sweepang, Arc2D.OPEN);
1305

    
1306
                        // Ellipse2D.Double elArco = new
1307
                        // Ellipse2D.Double(psArc.origin.x - psArc.primary_axis,
1308
                        // psArc.origin.y - psArc.secondary_axis,2.0 *
1309
                        // psArc.primary_axis, 2.0 * psArc.secondary_axis);
1310
                        IGeneralPathX elShapeArc = geomManager.createGeneralPath(
1311
                                IGeneralPathX.WIND_EVEN_ODD, 
1312
                                elArco.getPathIterator(null)
1313
                        );
1314

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

    
1321
                        if (dgnReader.getInfo().dimension == 3) {
1322
                            // Aqu? podr?amos hacer cosas con la coordenada Z
1323
                        }
1324

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

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

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

    
1365
                            if (psArc.type == DGNFileHeader.DGNT_ELLIPSE) {
1366
                                addShape(createSurface(elShapeArc), auxRow,
1367
                                    type, dgnReader);
1368
                            }
1369
                        }
1370

    
1371
                        break;
1372

    
1373
                    case DGNFileHeader.DGNST_TEXT:
1374

    
1375
                        DGNElemText psText = (DGNElemText) elemento;
1376
                        Geometry elShapeTxt = createPoint(psText.origin.x, psText.origin.y,
1377
                                psText.origin.z);
1378

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

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

    
1400
            if (bElementoCompuesto) {
1401
                if (bInsideCell) {
1402
                    auxRow = cellRow;
1403
                } else {
1404
                    auxRow = complexRow;
1405
                }
1406

    
1407
                addShape(createMultiCurve(elementoCompuesto), auxRow, type,
1408
                    dgnReader);
1409

    
1410
                if (bEsPoligono) {
1411
                    if (complex_index_fill_color != -1) {
1412
                        auxRow[ID_FIELD_COLOR] = complex_index_fill_color;
1413
                    }
1414

    
1415
                    addShape(createSurface(elementoCompuesto), auxRow, type,
1416
                        dgnReader);
1417
                }
1418
            }
1419

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

    
1430
        }
1431

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

    
1444
        }
1445

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

    
1460
            return point;
1461
        }
1462

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

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

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

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

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

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

    
1511
        }
1512

    
1513
    }
1514

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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