Well, currently, my map-chunk building program uses a ds_grid of ds_grids to store all that information. The top level is simply a ds_grid of the x and y coordinate of a given tile location. The sub-level ds_grid then contains the various pieces of information I need to draw the correct tile and the correct mask to blend it to the surrounding tiles and different tile sets (the tile texture type and sub-image, the type of mask it uses and the mask sub-image, and so on). _edit_ I could dispense with the 2-tier ds_grid structure and multiplied the x/y coordinates together instead, condensing the top-level 2d ds_grid into a 1d structure, using the second dimension to store the tile data itself instead of a reference to another ds_grid.
The truth is, there are a lot of ways to go about this -- some better or worse than others (I'll find out which one mine falls under eventually). An array would probably be faster, but since the data being used isn't fixed-length in nature and its size can change regularly, the functions available for ds_grids is worth the slower interaction (an array used in this way would get scattered all over GM's memory space). On the other hand, once I finalize the file format and start using it in the actual game I'm making, I will most likely move to an array structure, since the data will be used in a more read-only fashion. I won't need the ds_grid functions for manipulating the data it contains anymore.
The way I tackle animating certain tiles is by having an alarm that increments a global offset value. When the tile controller draws an animated tile, it checks the range of possible animation frames specific to that tile type (a data structure I created contains all that information), clamps the global offset to that range, and adds it to the sprite being drawn. I can do this because of the way GM assigns ID values to assets you load into the resource tree. Assets are numbered consecutively, starting from 0 at the top-most asset, and incrementing by 1 as you move down the list. So adding an offset value to the sprite argument of a draw_sprite() call redirects the draw call up or down the resource tree. This same trick can be used in a variety of other ways, such as changing which sprite you draw based on a point direction. Note: I have no reason to believe this behavior has changed with GMS2, but I felt it prudent to specify that this is for GMS1.4